Qt for MacOS上架App Store需要注意的系统定制化开发点

本文详细讲述了在使用Qt开发Mac应用程序时,开发者需面对的挑战,如对接SQLite(避免私有API)、使用WKWebview、处理系统皮肤切换、定制窗口标题栏、Dock图标右键功能以及优化Ping处理。
摘要由CSDN通过智能技术生成

用Qt开发Mac App超过5年时间,之前也单独用Cocoa上架了一款Mac软件,但如果考虑的是跨平台Win和Linux的需求,基本还是用Qt为主。Qt定制Mac下有几点是不得不考虑的:

1. 接入sqlite。必须用纯C的写法,QSqlDatabase那套API,苹果Mac App Store审核直接以私有api拒绝了.

int result = sqlite3_open(ch, &database);
sqlite3_exec(database, "PRAGMA synchronous = OFF; ", 0, 0, 0);

int ret = sqlite3_close(database);

int result = sqlite3_prepare_v2(database, sql, -1, &stmt, NULL);

2. 内嵌Web页面。浏览器只能用WKWebview,不能使用任何非Webkit内核的浏览器,比如CEF,WebEngine。同样会被苹果审核以使用私有api拒绝。

MacWebView *cWebView = [[[MacWebView alloc] initWithFrame:rect configuration:config target:this] autorelease];
webViewHandler = (WId)cWebView;
QWindow *webviewWin = QWindow::fromWinId(webViewHandler);
QWidget *webviewContainer = QWidget::createWindowContainer(webviewWin, this);

// 剩下操作都在WKNavigationDelegate和WKUIDelegate的回调中处理
@interface MacWebView: WKWebView {

    QPointer<WebViewInMac> pTarget;
}

3. 系统皮肤设置。苹果有浅色,深色和自动三种皮肤模式。Qt没有提供代码,只能使用苹果官方语言开发,代码如下:

QString DeviceInfo::getAppearanceName()
{
    NSAppearanceName appearName;
    if ([NSAppearance respondsToSelector:@selector(currentDrawingAppearance)])
    {
        appearName = NSAppearance.currentDrawingAppearance.name;
    }
    else
    {
        appearName = NSAppearance.currentAppearance.name;
    }

    if (NSAppearanceNameAqua == appearName)
    {
        return SKIN_WHITE;
    }
    else if (NSAppearanceNameDarkAqua == appearName)
    {
        return SKIN_BLACK;
    }

    return "";
}

void DeviceInfo::setAppearance(const QString& name)
{
    if (SKIN_WHITE == name)
        NSApp.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua];
    else if (SKIN_BLACK == name)
        NSApp.appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
    else
        NSApp.appearance = nil;
}

然后在MainWindow的changeEvent事件回调等待系统触发的皮肤切换事件

void MainWindow::changeEvent(QEvent *event)
{
#if defined(Q_OS_DARWIN)
    if (event->type() == QEvent::ThemeChange) // 注意改成ThemeChange,不是PaletteChange
    {
        // 需要针对自动模式特殊处理
        QString appearName = DeviceInfo::getAppearanceName();
        
    }
}

4. 窗口标题栏定制,设置高度,存放按钮,修改样式等。

// 设置窗体无边框
MainWindow w;
w.setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
NSView *view = (NSView *) w->winId();
NSWindow *window = [view window];
self.cocoaWin = window;
window.titlebarAppearsTransparent = YES;
window.titleVisibility = NSWindowTitleHidden;
window.backgroundColor = [NSColor colorWithRed:(float)53/255 green:(float)61/255 blue:(float)71/255 alpha:1.];
window.styleMask = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable
                       | NSWindowStyleMaskResizable | NSWindowStyleMaskTitled
                       | NSWindowStyleMaskFullSizeContentView;
window.opaque = NO;
window.delegate = self;
// 剩下就是新增NSView,NSButton的工作

//这里还需要注意NSWindow的回调
- (void) windowDidExitFullScreen:(NSNotification *)notification
{
    NSInteger major, minor;
    NSProcessInfo *processInfo = [NSProcessInfo processInfo];
    major = processInfo.operatingSystemVersion.majorVersion;
    minor = processInfo.operatingSystemVersion.minorVersion;

    NSWindow *window = notification.object;
    if ((major == 10 && minor < 14) || major < 10)
        window.styleMask = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable
                           | NSWindowStyleMaskResizable | NSWindowStyleMaskTitled;
    else
        window.styleMask = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable
                           | NSWindowStyleMaskResizable | NSWindowStyleMaskTitled
                           | NSWindowStyleMaskFullSizeContentView;
}

- (void) windowDidResize:(NSNotification *)notification
{

}    

5. Dock图标的右键退出功能实现,同样需要苹果官方语言开发

void action_showMain()
{
    foreach(QWidget* w, ((QApplication*)qApp)->topLevelWidgets())
    {
      if (MainWindow* mainWin = qobject_cast<MainWindow*>(w))
      {
          if (mainWin->isMinimized())
          {
              mainWin->showMaximized();
          }
      }
      else if (WebPanel* mainWin = qobject_cast<WebPanel*>(w))
      {
          if (mainWin->isMinimized())
          {
              mainWin->showNormal();
          }
      }
    }
}

bool applicationShouldHandleReopen(id self,SEL _cmd, ...)
{
    // 点击dock按钮时调用方法
    action_showMain();
    return false;
}

需要在main函数中启动方法替换

void setupDockClickEvent()
{
    Class cls = objc_getClass("NSApplication");
    id appInstance = ((id(*)(Class,SEL))objc_msgSend)(cls, sel_registerName("sharedApplication"));
    if(appInstance != nullptr)
    {
        id appDelegate = ((id(*)(Class,SEL))objc_msgSend)(appInstance, sel_registerName("delegate"));
        Class delClass =((id(*)(Class,SEL))objc_msgSend)(appDelegate, sel_registerName("class"));

        SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:");
        if (class_getInstanceMethod(delClass, shouldHandle))
        {
            // 使用Swizzling黑魔法替换 applicationShouldHandleReopen:hasVisibleWindows: 系统方法
            if (class_replaceMethod(delClass, shouldHandle,  reinterpret_cast<IMP>(applicationShouldHandleReopen), "B@:"))
                qDebug() << "applicationShouldHandleReopen方法替换成功";
            else
                qDebug() << "applicationShouldHandleReopen方法替换失败";
        }
        else
        {
            if (class_addMethod(delClass, shouldHandle, reinterpret_cast<IMP>(applicationShouldHandleReopen),"B@:"))
                qDebug() << "applicationShouldHandleReopen添加dock点击方法成功";
            else
                qDebug() << "applicationShouldHandleReopen添加dock点击方法成功失败";
        }

        SEL shouldTerminate = sel_registerName("applicationShouldTerminate:");
        if (class_getInstanceMethod(delClass, shouldTerminate))
        {
            if (class_replaceMethod(delClass, shouldTerminate,  reinterpret_cast<IMP>(applicationShouldTerminate), "B@:"))
                qDebug() << "applicationShouldTerminate方法替换成功";
            else
                qDebug() << "applicationShouldTerminate方法替换失败";
        }
        else
        {
            if (class_addMethod(delClass, shouldTerminate, reinterpret_cast<IMP>(applicationShouldTerminate),"B@:"))
                qDebug() << "applicationShouldTerminate添加dock点击方法成功";
            else
                qDebug() << "applicationShouldTerminate添加dock点击方法成功失败";
        }
    }
}

6. Ping处理,因Mac平台下断线重连比Win下要慢很多,故采用了PING服务器IP进行快速重连操作,这里封装了SimplePing的开源代码,故此不再展示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值