Using Qt to build an Omi App for iOS (and Android)
Working on projects where the technology is pre-determined, it’s often difficult to be able to deep-dive into familiar waters. Recently, we have been working on a new
service called Omi - and in that context I was able to use two weeks to knock together a prototype of a mobile app for it using Qt and QML. With the help of QML, we were able to build a responsive and functional UX in less than two weeks
What follows is a bit of a braindump of trips and ticks I found or discovered. For those expecting a more complete end-result, I apologise in advance; This is all I managed to cobble together before moving on to new projects ;)
Initial project setup
Initially
we created a project using the default “Qt Quick Application” template shipped with Qt Creator. This works pretty well and in my opinion Qt Creator is the best IDE to use for day-to-day development of Qt-based iOS apps – until you want to start packaging your
App for testing or release. At this point you need to switch to Xcode to define launch image, icons etc – as well as various other .plist entries. I copied the pre-generated .plist and used it with QMAKE_INFO_PLIST
instead of having qmake overwrite things like the Facebook App ID.
= myapp.plist
Networking
Initially I started building a backend singleton where a QNetworkAccessManager
subclass
would marshal all calls to our backend API. However, after some helpful advice from our newest team member, Jens, I built up all the basic serverside interaction using only JavaScript and XMLHttpRequest
.
One key issue with Qt networking on iOS was caught in the response handlers for XMLHttpRequest
.
The status property would always return 0;
var client = new XMLHttpRequest()
client.open("GET", "https://api.domain.com/entity")
client.onreadystatechanged = function() {
console.log(client.status) // would always return '0'
}
client.send()
As it turns out, this is because SSL support is not built-in due to export restrictions. That means you most likely need to compile in support for OpenSSL. I’ll leave that for a later post. If you have already done and have any helpful tips, please leave a
comment in QTBUG-38864.
Facebook login
In mobile apps you typically authenticate using OAuth or similar technologies, where you end up with a token or cookie that you can re-use. To build the authorization token to pass to the server, Qt.btoa()
is
used to create a base64 string combining app id and secret;
var client = new XMLHttpRequest();
var authorizationHeader = "Basic "+Qt.btoa(AppVars.appKey+":"+AppVars.appSecret);
client.setRequestHeader("Authorization", authorizationHeader);
Then, the returned session token can be saved in persistent settings;
import Qt.labs.settings 1.0 Settings {
id: settings
property string token
} onAuthenticated: {
settings.token = authenticatedToken
}
This is naturally not very safe (and given it’s not passed over SSL it’s actually flat out dangerous in the current state)- it should rather be stored in the Keychain to prevent from outside tampering and inspection. But, it’s easy enough to hook that in at
a later stage.
Facebook login is provided more or less for you in the form of the FacebookSDK. Typically I would use CocoaPods to add this dependency in iOS Apps – but qmake breaks down with the .xcworkspace files required by CocoaPods. The alternative becomes manually adding
the framework to your project, and linking against it;
iphoneos {
QMAKE_LFLAGS += -F /Users/hhartz/Documents/FacebookSDK/
LIBS += -framework FacebookSDK
HEADERS += qtfacebook.h
OBJECTIVE_SOURCES += qtfacebook.mm
}
The qtfacebook.* files provide the API for login and response handling. Basically, it’s a simple stack object exposed to QML;
QtFacebook facebook;
engine.rootContext()->setContextProperty("facebook", &facebook);
This can be called when the user clicks a facebook login button, where a slot is invoked to request a token for the App’s facebook ID;
void QtFacebook::login()
{
if (FBSession.activeSession.state == FBSessionStateOpen || FBSession.activeSession.state == FBSessionStateOpenTokenExtended) {
[FBSession.activeSession closeAndClearTokenInformation];
} else {
[FBSession openActiveSessionWithReadPermissions:@[@"public_profile"]
allowLoginUI:YES
completionHandler:
^(FBSession *session, FBSessionState state, NSError *error) {
Q_UNUSED(state)
Q_UNUSED(error)
NSString *accessToken = session.accessTokenData.accessToken;
if (accessToken.length>0) {
emit(loggedInWithToken(QString(accessToken.UTF8String)));
}
}];
}
over in QML land, the loggedInWithToken
signal is connected
to a method that logs us on to our service with the supplied access token.
Data model
To represent the data model a Schema.js class is used which defines JavaScript objects using prototypes.
function User(userJsonData) {
this._id = userJsonData._id
this.first_name = userJsonData.first_name
this.middle_name = userJsonData.middle_name
this.last_name = userJsonData.last_name
this.email = userJsonData.email
}
User.prototype.avatarUrl = function() {
var mailHash = Qt.md5(this.email.trim())
return qsTr("http://www.gravatar.com/avatar/%1").arg(mailHash)
}
These are used when parsing the response JSON entities returned from the API, and stored in-memory.
// assuming 'this' has a property friendsModel to the datastructure
var friendsJSONArray = JSON.parse(client.responseText)
friendsJSONArray.forEach(function(friendJSONData) {
this.friendsModel.push(new Schema.User(object))
})
The challenging aspect of this technique is that QML mangles the objects, so if you have a JSON entity (e.g. an array) as a member of your object – it’s converted to a ListModel data entry. That naturally means using builtin JavaScript Array prototypes are
out of the question and it kind of breaks the coding style.
In the long run, it’s more flexible to build a native code model – or perhaps use a thirdparty solution that lets you represent JSON objects returned from a URI more elegantly. One very interesting approach is discussed in How
Dropbox Uses C++ for Cross-Platform iOS and Android Development, which describes an architecture similar to Core Data (thanks to Jason)
Native integration
A key part of your iOS App are the UIApplicationDelegate
protocal
callbacks. This is used to implement support for push notifications, facebook login and many other things. However, Qt for iOS has no obvious hook to be able to respond to these callbacks. Categories to the rescue!
You can use categories to override the builtin iOS app delegate, to allow e.g. integration with the iOS FacebookSDK;
#import "QIOSApplicationDelegate+OmiAppDelegate.h"
#import <FacebookSDK/FacebookSDK.h>
@implementation QIOSApplicationDelegate (OmiAppDelegate)
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
}
@end
Similar approaches would be used to implement support for push notificaitons.
This can subsequently be included in your project by adding the relevant files to your project;
OBJECTIVE_SOURCES += QIOSApplicationDelegate+MyAppDelegate.m
Image Resources
To add splash screen and icons, I highly recommend using Image catalogues. To add these to your iOS app, create a new Image catalogue, and add them to the xcodeproj before building for device. Unfortunately I’m not aware of a better way to include an image
catalogue in your .pro file.
UI Icons
Since we use FontAwesome for our icons, it was rather trivial to get the same icons in the QML UI; Simply add FontAwesome as a resource, and load it in your QML.
FontLoader {
source: "qrc:/font/fontawesome-webfont.ttf"
}
Then, with the help of the (slightly outdated) fontawesome.js file from Using Fonts Awesome in QML it becomes trivial to create a nice flat icon;
Text {
font.family: "FontAwesome"
text: FontAwesome.Icon.Reorder
font.pixelSize: 20
}
Embedded in a circle, it looks like this;
Scalability
Jens came up with a nice trick to solve scalability (at least across his Android device and my iPhone 5s); define a
property real scaleFactor: Math.min(window.width,window.height) / 320
property which can be used all over the QML sources in place of fixed constants. This means we can define a font size to e.g.
font.pixelSize: scaleFactor * fontSize
Next steps?
With the little time we invested in it, I think the App turned out pretty decent. There are still issues like Uncanny Valley effect to things like list bouncing etc which doesn’t
feel 100% native. However, these are small details in the larger scheme of things which can be worked around or will be fixed upstream in the near future. It’s clear to me that Qt is a viable technology for Mobile Apps on iOS and Android and will help us in
being able to build mobile Apps faster.
Using Qt to build an Omi App for iOS (and Android)的更多相关文章
- APP在iOS和Android的推送规则
因为iOS和Android不同的规则,下边将iOS和Android能接收到通知的详细情 形进行说明(前提:APP已经在设备上安装并登录过): iOS: APP未 ...
- Instant App 即将到来,Android 集权或将加速分裂
在境外,Android 的体验将越来越好,在中国,Android 的更新可能将止步于6.0! 话题讨论:Instant App 在中国将何去何从? 以下为谷歌原创文章 2017-03-03 Googl ...
- Cordova开发App入门之创建android项目
Apache Cordova是一个开源的移动开发框架.允许使用标准的web技术-HTML5,CSS3和JavaScript做跨平台开发. 应用在每个平台的具体执行被封装了起来,并依靠符合标准的API绑 ...
- Analysis of the Facebook.app for iOS
Analysis of the Facebook.app for iOS Posted Oct 18, 2016 Did you ever wonder why the Facebook.app fo ...
- App Shortcuts 快捷方式:Android 的 '3D Touch'
Hello Shortcuts 从Android7.1(API level25)开始,开发者可以为自己的app定制shortcuts.shortcuts使用户更便捷.快速的使用app.我个人感觉有点像 ...
- Livecoding.tv 现正举行iOS及Android App设计比赛
近日,Livecoding.tv, 一个为世界各地的程序员提供在线实时交流的平台,在其网站上发布了一篇通知, 宣布从4月15日至5月15日,会为iOS和Android的开发者举办一场本地移动app设计 ...
- 不可或缺 Windows Native (25) - C++: windows app native, android app native, ios app native
[源码下载] 不可或缺 Windows Native (25) - C++: windows app native, android app native, ios app native 作者:web ...
- To create my first app in iOS with Xcode(在Xcode创建我的第一个iOS app )
To create my first app in iOS create the project. In the welcome window, click “Create a new Xcode p ...
- javascript开发 ios和android app的简单介绍
先看几个名词解释: nodejs ionic,Cordova,phoneGap,anjularjs react-native,reactjs nodeJs 的介绍参见这里,写的很好http://www ...
随机推荐
- html5新特性:利用history的pushState等方法来解决使用ajax导致页面后退和前进的问题
一.背景 使用ajax,可以实现不需要刷新整个页面就可以进行局部页面的更新.这样可以开发交互性很强的富客户端程序,减少网络传输的内容.但长期以来存在一个问题,就是无法利用浏览器本身提供的前进和后退按钮 ...
- perl 传递对象到模块
perl 中的对象 就是引用 通过new方法传递数据结构给各个模块 [root@wx03 test]# cat x1.pm package x1; use Data::Dumper; sub new ...
- Mixtile LOFT
日前,国内电子原型类开发团队Mixtile(深圳致趣科技)新推出的 Mixtile LOFT套件,受到业内著名的海外科技网站CNXSoft的关注和报道. 如果要阅读相关的原文报道,可点击这里.下面摘录 ...
- bin文件格式分析
xip 的 bin 文件分析 一个bin 文件在存储上是按以下的结构存储的 组成:标记(7)+Image開始地址(1)+Image长度(1) 记录0地址+记录0长+记录0校验和+ ...
- android的JNI标准 android的NDK
转载的! Java Native Interface (JNI)标准是java平台的一部分,它允许Java代码和其他语言写的代码进行交互.JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) ...
- Windows Phone 8初学者开发—第23部分:测试并向应用商店提交
第23部分: 测试并向应用商店提交 原文地址:http://channel9.msdn.com/Series/Windows-Phone-8-Development-for-Absolute-Begi ...
- 强大的Http监控工具Fidder
软件下载:http://fiddler2.com/get-fiddler 软件学习:http://www.cnblogs.com/TankXiao/archive/2012/02/06/2337728 ...
- 减少HTTP请求之合并图片详解(大型网站优化技术)
原文:减少HTTP请求之合并图片详解(大型网站优化技术) 一.相关知识讲解 看过雅虎的前端优化35条建议,都知道优化前端是有多么重要.页面的加载速度直接影响到用户的体验.80%的终端用户响应时间都花在 ...
- 根据li标签 查找class="alcw4 alcw41"对应的值
jrhmpt01:/root/lwp/0526# cat a2.pl use LWP::UserAgent; use DBI; use POSIX; use Data::Dumper; use HTM ...
- uva 568(数学)
题解:从1開始乘到n,由于结果仅仅要最后一位.所以每乘完一次,仅仅要保留后5位(少了值会不准确,刚開始仅仅保留了一位.结果到15就错了,保留多了int会溢出,比方3125就会出错) 和下一个数相乘,接 ...