Qt6中在C++头文件嵌入Objective-C代码的实现问题
解决方案:在C++头文件中实现iOS打开应用设置功能
问题背景
我习惯在C项目中仅使用.h文件(因脚本较短,无需生成大量文件)。目前有一个纯头文件的C脚本,其中部分Objective-C代码可正常运行,但iOS端打开应用设置的代码无法工作:代码无法识别NSURL,且引入<UIKit/UIKit.h>会触发编译错误。需要在不单独创建.mm文件的前提下,在C++头文件中实现iOS打开应用设置的功能。
修改后的完整代码
#ifndef PERMISSION_H #define PERMISSION_H #include <QMainWindow> #include <QMessageBox> #include <QtCore/QObject> #include <QtCore/QMetaObject> #include <QUrl> #include <QDesktopServices> #include <qdebug.h> #if defined(Q_OS_ANDROID) #include <QtCore/qjniobject.h> #include <QtCore/qcoreapplication.h> #include <QtCore/private/qandroidextras_p.h> #elif defined(Q_OS_IOS) #include <objc/objc.h> #include <objc/message.h> #include <objc/runtime.h> // 避免引入UIKit头文件,用runtime方式调用API static NSString *const UIApplicationOpenSettingsURLString = @"app-settings:"; // 可选:直接跳转到照片权限设置的URL(iOS 10+可用) static NSString *const PHOTOS_PERMISSION_SETTINGS_URL = @"App-Prefs:root=PHOTOS"; #endif class Permission : public QObject { Q_OBJECT public: Permission(QObject *parent = nullptr) : QObject(parent) {} // 申请相册权限 Q_INVOKABLE bool requestMedia() { #if defined(Q_OS_ANDROID) // Android端代码(已正常工作,略) #elif defined(Q_OS_IOS) __block BOOL isAuthorized = NO; id phPhotoLibrary = objc_getClass("PHPhotoLibrary"); SEL requestAuthSel = sel_registerName("requestAuthorization:"); typedef void (^CompletionHandler)(int); CompletionHandler completionHandler = ^(int status) { switch (status) { case 3: // PHAuthorizationStatusAuthorized qDebug() << "相册权限已授予"; isAuthorized = YES; break; case 1: // PHAuthorizationStatusDenied case 2: // PHAuthorizationStatusRestricted qDebug() << "相册权限已拒绝"; isAuthorized = NO; break; case 0: // PHAuthorizationStatusNotDetermined qDebug() << "相册权限未确定"; isAuthorized = NO; break; } }; ((void (*)(id, SEL, CompletionHandler))objc_msgSend)(phPhotoLibrary, requestAuthSel, completionHandler); return isAuthorized; #endif return true; // Windows等其他平台默认返回有权限 } // 打开应用设置(相册权限相关) Q_INVOKABLE void settingsMedia() { #if defined(Q_OS_ANDROID) // Android端代码(已正常工作,保留原实现) QJniObject activity = QNativeInterface::QAndroidApplication::context(); if (!activity.isValid()) { qCritical() << "Activity无效"; return; } QJniObject bundle = QJniObject::fromString("package:com.RESTOFMYBUNDLEID"); if (!bundle.isValid()) { qCritical() << "Bundle无效"; return; } QJniObject uri = QJniObject::callStaticObjectMethod("android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", bundle.object<jstring>()); if (!uri.isValid()) { qCritical() << "Uri无效"; return; } QJniObject packageName = QJniObject::fromString("android.settings.APPLICATION_DETAILS_SETTINGS"); if (!packageName.isValid()) { qCritical() << "Package名称无效"; return; } QJniObject intent = QJniObject("android/content/Intent","(Ljava/lang/String;)V", packageName.object<jstring>()); if (!intent.isValid()) { qCritical() << "Intent无效"; return; } intent.callObjectMethod("addCategory", "(Ljava/lang/String;)Landroid/content/Intent;", QJniObject::fromString("android.intent.category.DEFAULT").object<jstring>()); intent.callObjectMethod("setData", "(Landroid/net/Uri;)Landroid/content/Intent;", uri.object<jobject>()); QtAndroidPrivate::startActivity(intent.object<jobject>(), 10101); #elif defined(Q_OS_IOS) // 用Objective-C Runtime实现打开设置,避免引入UIKit头文件 // 1. 获取UIApplication单例 id uiAppClass = objc_getClass("UIApplication"); SEL sharedAppSel = sel_registerName("sharedApplication"); id uiAppInstance = ((id (*)(id, SEL))objc_msgSend)(uiAppClass, sharedAppSel); // 2. 创建设置页面的NSURL id nsUrlClass = objc_getClass("NSURL"); SEL urlWithStringSel = sel_registerName("URLWithString:"); // 可选:把UIApplicationOpenSettingsURLString换成PHOTOS_PERMISSION_SETTINGS_URL直接跳转到相册权限页 id settingsUrl = ((id (*)(id, SEL, NSString*))objc_msgSend)(nsUrlClass, urlWithStringSel, UIApplicationOpenSettingsURLString); // 3. 检查是否能打开该URL SEL canOpenUrlSel = sel_registerName("canOpenURL:"); BOOL canOpen = ((BOOL (*)(id, SEL, id))objc_msgSend)(uiAppInstance, canOpenUrlSel, settingsUrl); if (canOpen) { // 4. 打开URL(iOS 10+用openURL:options:completionHandler:) SEL openUrlSel = sel_registerName("openURL:options:completionHandler:"); // 空字典(options参数) id emptyDict = objc_msgSend(objc_getClass("NSDictionary"), sel_registerName("dictionary")); typedef void (^OpenCompletion)(BOOL); OpenCompletion completion = ^(BOOL success) { if (success) { qDebug() << "已打开应用设置"; } else { qDebug() << "打开应用设置失败"; } }; ((void (*)(id, SEL, id, id, OpenCompletion))objc_msgSend)(uiAppInstance, openUrlSel, settingsUrl, emptyDict, completion); } #endif } }; #endif // PERMISSION_H
关键实现说明
- 规避头文件冲突:完全通过Objective-C Runtime API调用功能,用
objc_getClass获取类、sel_registerName注册方法选择器,无需引入<UIKit/UIKit.h>。 - 手动替换宏定义:直接声明
UIApplicationOpenSettingsURLString的字符串值,替代UIKit中的系统宏。 - 版本兼容:使用iOS 10及以上版本推荐的
openURL:options:completionHandler:方法,同时保留canOpenURL:的兼容性检查。
额外技巧:直接跳转到相册权限设置
如果想跳过应用主设置页,直接打开相册权限的专属设置界面,可将创建URL时的UIApplicationOpenSettingsURLString替换为PHOTOS_PERMISSION_SETTINGS_URL(代码中已预定义)。注意该私有URL仅在iOS 10及以上版本可用,Apple可能会调整格式,使用前需充分测试。
内容的提问来源于stack exchange,提问作者Jiri Zaloudek
相关产品推荐
相关产品推荐

