You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 23:53:14