既然要聊 Qt 混合 OC 編程,首先要簡單介紹一下 Objective C 。我只有一句話:Go,問搜索引擎去。由於我所知實在有限,怕誤導了您。固然若是您不怕,往下看吧。html
首先我要說一下 Objective C 的源文件,後綴是.m 或 .mm ,在 .mm 文件裏,能夠直接使用 C++ 代碼。因此,咱們要混合 Qt 代碼與 OC 代碼,就須要在 Qt 項目里加入 mm 文件。ios
Qt SDK for Mac ,安裝以後, Qt Creator 會使用 XCode 提供的編譯工具鏈來編譯代碼,可以正確編譯 mm 文件,也能夠連接 iOS 的庫文件。objective-c
而要混合 OC 代碼,須要更改一下 pro 文件。一個是添加 mm 文件,一個是鏈接針對 iOS 的庫文件。編程
添加源文件,使用 OBJECTIVE_SOURCES 這個變量,好比醬紫:markdown
OBJECTIVE_SOURCES += ocview.mm
連接庫 XCode 提供的庫,則須要使用 QMAKE_LFLAGS ,相似醬紫:app
ios {
QMAKE_LFLAGS += -framework OpenGLES QMAKE_LFLAGS += -framework GLKit QMAKE_LFLAGS += -framework QuartzCore QMAKE_LFLAGS += -framework CoreVideo QMAKE_LFLAGS += -framework CoreAudio QMAKE_LFLAGS += -framework CoreImage QMAKE_LFLAGS += -framework CoreMedia QMAKE_LFLAGS += -framework AVFoundation QMAKE_LFLAGS += -framework AudioToolbox QMAKE_LFLAGS += -framework CoreGraphics QMAKE_LFLAGS += -framework UIKit }
上面是我使用 Qt 針對 iOS 編程的配置。我使用了不少針對 iOS 的庫,因此添加了不少 framework 。框架
「 -framework UIKit 」這種參數,是經由 Makefile 傳遞給 Clang 的參數,-framework 是用來指示要連接某個框架(或者說庫)的關鍵字,它後面跟的是框架(庫)名。iview
須要注意的是,咱們使用針對 iOS 的庫,不是經過「 LIBS += 」這種方式來引入哦。固然,你本身經過 Qt 實現的 .a 庫,依然須要使用「 LIBS += 」這種方式。ide
有時你須要爲你的項目指定 plist 文件, plist 文件全名是 Property List ,後綴是 .plist 。它用來定義 iOS 應用的屬性,好比 Bundle(iOS上的一個應用被稱爲一個 Bundle ) 的顯示名字、可執行文件名字、簽名、證書等等,固然也能夠保存一些配置數據。具體的介紹參考 iOS 開發的文檔吧。函數
要在 pro 文件裏添加 plist ,要使用 QMAKE_INFO_PLIST 關鍵字。以下面醬紫:
QMAKE_INFO_PLIST += MultiWindow.plist
好啦,關於 pro 文件中與混合使用 OC 相關的配置項,大致就這些了。接下來咱們看如何寫 Objective C 代碼啦。
乖乖,很惶恐啊,這是個人弱項,沒寫過多少 OC 代碼。因此,請不要問我 OC 有關的問題,我真不知道……
個人示例,是在 QML 的界面上疊加iOS原生的界面,即 UIView、UIWindow之類的。由於 OC 是 C 的近親,和 C++ 有着自然的血緣,混合起來特別方便哈,比 Android 上使用 JNI 編程好用多了。
不過有一點, OC 都適用 [] 這種語法來調用函數,使用 XCode 的話,語法提示和自動完成功能很是強大,基本不用思考的就能找到你要用的函數。而 Qt Creator 麼,嘿嘿,就沒這麼好相與了,純粹要手寫哦。我當時都是開着 XCode 看 API 文檔找的,比較痛苦。
我測試時的示例,用的是 Qt Quick App 項目模板,使用 QQuickView 來加載 QML 文檔。這裏也以此爲例來講明。關於Qt Quick,能夠看我博客的專欄,也能夠看個人書《Qt Quick核心編程》,書裏系統詳細的介紹了Qt Quick的基本概念並提供了豐富的示例。
首先要說 QQuickView 究竟是什麼。
QQuickView 呢,實際上是一個 UIView 。UIView 則是 iOS 開發框架裏不少界面元素的根兒,好比 UIWindow 就是 UIView 的子類。
Qt 的 QQuickView 是一個 UIView ,建立了 QQuickView 實例後,就有了一個 UIView ,而後 Qt 玩了一些魔法,拿到了 UIView 的 OpenGL Context ,跑起了 Qt 的事件循環,在這個 OpenGL Context 上從零開始繪製了本身的場景和 UI 系統。
就這麼簡單,你能夠查閱 Qt 源碼來進一步瞭解。
須要注意的是, QML 界面元素的渲染,與 UIView 這種原生界面的渲染,不在一個線程中。並且 iOS 對 OpenGL ES 的支持很好,你能夠同時使用多個 OpenGL Context 。更好的是,你能夠窗口模式來建立一個 OpenGL Context 。不像 Android 版本哦, Qt 使用 OpenGL 的時候都是全屏模式,局部更新不支持,因此咱們在 Android 上使用 QML 裏的 Camera 和 VideoOutput 來開發拍照應用時, VideoOutput 必須是全屏模式(必須fill_parent)。而在 iOS 上,則沒有這個限制了。看來 iOS 仍是很美好的啦。
我靠,扯得有點兒遠,最近寫技術文章少了,愈來愈羅嗦了。言歸正傳吧。
由於 QQuickView 實際上就是一個 UIView ,因此能夠強制轉換爲 UIView ,而後使用 OC 的方法來建立新的 UIView 或者 UIWindow ,這樣就有了原生的 UI 組件了,你能夠在這個原生的 UI 組件上使用 OpenGL 繪製本身的東西或者添加其它原生的控件,很是美好。
不過要說明的是,經過這種方法建立出來的 iOS 原生界面元素,會始終在 QML 界面之上,把 QML 場景裏的界面元素給蓋住。
要使用 OC 的類庫,須要在 mm 文件內包含相關的頭文件,又有幾部分工做要作。一個是在 pro 文件里加入 SDK 路徑,使用 INCLUDEPATH 變量便可,很少說了。另一點是在 mm 文件內包含 OC 的頭文件,與 C++ 頭文件一個理兒,不過要使用 #import 哦。相似醬紫:
#import <UIKit/UIKit.h> #import <GLKit/GLKit.h>
包含了頭文件,就可使用 OC 類庫了。好比我要在 QQuickView 上面建立一個新的 iOS 原生的 UIView ,.mm 文件裏能夠這樣:
void addOCView(QQuickWindow *w) { UIView *view = reinterpret_cast<UIView *>(w->winId()); CGRect viewRect = CGRectMake(10, 10, 100, 100); UIView* myView = [[UIView alloc] initWithFrame:viewRect]; [myView setBackgroundColor:[UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0]]; [view addSubview: myView]; }
如你所見,我寫了一個 addOCView 方法,它的參數是 QQuickView 。在 addOCView 方法裏,我把 QQuickView 強制轉換爲 UIView 來使用。
我建立了一個新的 UIView ,設置了它的背景顏色,而後把它添加爲 QQuickView 的子窗口。諾,就這麼簡單了。
說下 main.cpp ,看它如何使用 addOCView() 方法。代碼以下:
int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView viewer; viewer.setResizeMode(QQuickView::SizeRootObjectToView); viewer.setSource(QUrl("qrc:/main.qml")); viewer.show(); addOCView(&viewer); return app.exec(); }
一點兒都不驚喜是吧,就是直接調用了 addOCView 哦。哈哈,確實如此了。
混合使用 iOS 原生界面時,也能夠達到原生界面與 QML 界面的無縫集成。關鍵就在於計算 QML 界面元素的位置,而後根據 QML 界面元素的位置來設置原生界面的位置。
QML 提供了換算元素位置的方法,Item 有個方法,叫做 mapToItem() ,它能夠把一個相對於 Qt Quick Item 的區域映射到另外一個 Item 上,獲得座標了。好比你的 qml 文件是下面的樣子:
Rectangle {
... Rectangle { id: videoLayer; anchors.margins: 8; anchors.left: parent.left; anchors.right: parent.right; anchors.top: parent.top; anchors.bottom: actionBar.top; color: "green"; } ... }
你想把原生的 UIView 定位到 id 爲 videoLayer 的元素內部,能夠相似下面換算一個座標並使用它:
var coordinate = videoLayer.mapToItem(null, 8, 8, videoLayer.width - 16, videoLayer.height - 16); winUtil.addUIView(coordinate.x, coordinate.y, coordinate.width, coordinate.height);
換算出的coordinate有 x 、 y 、 width 、 height 屬性。當 mapToItem 的第一個參數爲 null 時,換算的結果就是相對於 QQuickView 的。那咱們在添加 UIView 時,就能夠用這個換算結果來構造一個 CGRect 對象,用這個 CGRect 來初始化 UIView 的位置。
前面示例代碼中的 winUtil 是我在 C++ 內實現的一個輔助類,導出到了 QML 環境中。它的 addUIView 方法根據傳入的座標來設置原生 UIView 的位置。參考代碼以下:
UIView *v = reinterpret_cast<UIView*>(view->winId()); uiw = [[UIWindow alloc] initWithFrame:CGRectMake(x, y, width, height)]; [v addSubview: uiw];
上面代碼中,view 是 QQuickView 。此次咱們建立 UIView 時使用了傳入的 x 、 y 、 width 、 height,這樣新建的 UIView 就和 QML 元素整合在一塊兒了,看起來好像是一體的。
OK,這就是所有了。