标签存档: iOS

[Tips] 适配 iOS App 到 iPhone 5 和 iOS 6

iPhone 5 和 iOS 6 正式发布后,iOS 开发者们就要开始做 iPhone 5 和 iOS 6 的适配工作了 :)

在迁移我自己的几个 iOS App 过程中,发现了老的 Xcode 工程代码在 iOS 6 中无法响应屏幕旋转的问题,在这里记录一下。顺便记录一下怎样适配 iOS App 到 iPhone 5 给还不知道怎么做的朋友们。

适配 iPhone 5 的 4 寸屏幕


iOS App LetterBox Mode

iPhone 5 的屏幕分辨率为 640×1136,默认情况下,老的 iOS App 会以上下加两条黑边的模式来运行,程序实际占用的分辨率还是 640×960,保证了原有的 iOS App 在不修改的情况下也可以正常运行。

如果需要适配 iPhone 5 的 4 寸屏幕,是一件很简单的事,只需要添加一张对应 iPhone 5 启动图片即可。

iPhone 5 的分辨率为 640×1136,所以只需要添加一张分辨率为 640×1136 的启动图片到 iOS App 工程即可。启动图片需要命名为 Default-568h@2x.png,因为 iPhone 5 没有非 Retina 屏幕的型号,所以不需要 Default-568h.png 这样一个文件了。

添加了对应 iPhone 5 分辨率的启动图片后,iOS App 就会以 640×1136 的分辨率运行了。

当然了,如果 iOS App 里在计算 view 的 frame 时,使用了固定的 320×480 来计算的话,在显示时就会有一些问题,这时候就需要动态根据 view 的 frame 大小去计算,而不是使用 Hard Code。

另外,也可以灵活使用 autoresizingMask,这样在未来 iPhone 又有其他分辨率,又或者出现了 iPad mini 的时候更方便处理。

解决旧的 iOS App 在 iOS 6 中无法随屏幕旋转的问题

如果你的 iOS App 支持随屏幕旋转而旋转,但是到了 iOS 6 中,发现这个功能不生效了,那么就要检查一下是不是因为旧版本的 Xcode 创建工程的原因。

但是在 iOS SDK Release Notes for iOS 6.0 中的 UIKit 一节中说到“Autorotation is changing in iOS 6. In iOS 6, the shouldAutorotateToInterfaceOrientation: method of UIViewController is deprecated. In its place, you should use the supportedInterfaceOrientationsForWindow: and shouldAutorotate methods.”,并且详细解释中也说明了 iOS 6 中的改动:

More responsibility is moving to the app and the app delegate. Now, iOS containers (such as UINavigationController) do not consult their children to determine whether they should autorotate. By default, an app and a view controller’s supported interface orientations are set to UIInterfaceOrientationMaskAll for the iPad idiom and UIInterfaceOrientationMaskAllButUpsideDown for the iPhone idiom.

A view controller’s supported interface orientations can change over time—even an app’s supported interface orientations can change over time. The system asks the top-most full-screen view controller (typically the root view controller) for its supported interface orientations whenever the device rotates or whenever a view controller is presented with the full-screen modal presentation style. Moreover, the supported orientations are retrieved only if this view controller returns YES from its shouldAutorotate method. The system intersects the view controller’s supported orientations with the app’s supported orientations (as determined by the Info.plist file or the app delegate’s application:supportedInterfaceOrientationsForWindow: method) to determine whether to rotate.

The system determines whether an orientation is supported by intersecting the value returned by the app’s supportedInterfaceOrientationsForWindow: method with the value returned by the supportedInterfaceOrientations method of the top-most full-screen controller.

The setStatusBarOrientation:animated: method is not deprecated outright. It now works only if the supportedInterfaceOrientations method of the top-most full-screen view controller returns 0. This makes the caller responsible for ensuring that the status bar orientation is consistent.

For compatibility, view controllers that still implement the shouldAutorotateToInterfaceOrientation: method do not get the new autorotation behaviors. (In other words, they do not fall back to using the app, app delegate, or Info.plist file to determine the supported orientations.) Instead, the shouldAutorotateToInterfaceOrientation: method is used to synthesize the information that would be returned by the supportedInterfaceOrientations method.

说明中提到,在 iOS 6 中系统会通过查询 rootViewController 来判断是否可以旋转视图,但是在以前,使用 Xcode 创建一个工程时,通常会有以下代码来显示主窗口:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    // Add the tab bar controller's view to the window and display.
    [self.window addSubview:tabBarController.view];
    [self.window makeKeyAndVisible];
 
    return YES;
}

在这里,并没有设置 self.window 的 rootViewController,而且在 MainWindow.xib 中,也没在连接 window 的 rootViewController 到默认的 UITabBarController。

因此,我们有两种方法来解决这个问题:

1. 在代码中设置 rootViewController

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    // Add the tab bar controller's view to the window and display.
    self.window.rootViewController = tabBarController;
    [self.window addSubview:tabBarController.view];
    [self.window makeKeyAndVisible];
 
    return YES;
}

2. 在 MainWindow.xib 中连接 window 的 rootViewController

两种方法任意选择一种就行了。

当然使用新版本 Xcode 创建的工程应该是没问题的,而且新版本 Xcode 的工程模板通常会使用代码来创建 rootViewController,而且默认也不再生成 MainWindow.xib。

参考资料

  1. iOS SDK Release Notes for iOS 6

—EOF—

推开窗户看世界 — Objective-C之外

引子

App Store 如今正风靡世界,许多人都想尝试去自己开发一个 iOS App,不过苹果官方推荐使用的是 Objective-C 这个语言。而 Objective-C 与 C、Java 之类的语言风格差异有些大,短时间并不容易掌握,又或者许多人只是想尝试开发一个可以运行在 iOS 上的程序而已,不想再去额外去学一门新的语言,那这个时候就可以考虑另外的一些技术来开发 iOS App。

在移动开发领域,得益于现在手机的性能越来越好,浏览器的功能越来越强大,JavaScript 也成为一门很流行的程序开发语言,而对应到 iOS 平台上,也有多种使用 JavaScript 来开发 iOS App 的技术。这次要介绍的三种技术之中,就有两种使用了 JavaScript。

另外,许多开发跨平台移动应用开发的解决方案,首选要支持的就是 iOS,因此在 iOS 这个平台上可以看到很多种不使用 Objective-C 去开发 iOS App 的技术,而且其中技术所编写出来的程序,除了可以在 iOS 上运行之外,还可以运行在 Android 以及其他系统平台上。能少写一份代码,何乐而不为呢~

不使用 Objective-C

这次要介绍的是三个不使用 Objective-C 来开发 iOS App 的解决方案:

这三个解决方案各有特点,它们的运行机制也有所不同,至于在开发 iOS App 时选用何种方案,可以根据每种方案不同的特点去选择适合自己的。

Titanium Mobile

Titanium Mobile 是 AppCelerator 推出的一个基于 JavaScript 的跨平台移动应用开发技术。通过 Titanium Mobile,可以直接使用 JavaScript 开发能运行于 iOS 和 Android 平台的应用程序,而代码只需要写一次。

Titanium Mobile 是通过将 JavaScript 代码映射到对应平台的 Native Code,在 Titanium Mobile 中操作某一个 UI 对象,或者是其他对象时,实时上是在操作对应平台中实际的 UI 对象,例如使用 Ti.UI.createView() 创建一个视图,它在 iOS 中运行时会实际对应到 UIView,使用 Ti.UI.createTabGroup() 就会对应到 iOS 中的 UITabBarController。这样做的好处就是,可以直接使用 JavaScript 来创建出原生的 UI,而不需要使用额外的代码来让程序 UI 更像系统的 UI。

另外,AppCelerator 在收购了 Apatna 之后,推出了适用于 Titanium 的 IDE Titanium Studio,通过使用 Titanium Studio,可以很方便的创建项目、带智能提示的编辑器,以及很方便的调试项目。

在 Titanium Studio 中调试 iOS 项目时,可以做到单步调试,也就意味着,可以拥有不弱于使用 Xcode 开发项目的体验。断点、单步、变量查看功能一应具全。如果需要发布到 App Store,也可以很方便的通过图形化界面来打包。

当然,使用 Titanium Studio 也需要配合 Xcode 来使用,必须要在安装了 Xcode 的情况下,才可以使用 Titanium Studio 来调试和测试项目。

在 Titanium Studio 推出之前,Titanium Mobile 是靠一个 Developer Tool 来进行打包等操作的,相对比较繁琐,而有了 Titanium Studio 之后,让一切变得简单,我也是从那时开始关注 Titanium Mobile。

对于前端开发工程师来说,只需要理解了 iOS 开发中的一些概念,就可以使用 Titanium Mobile 来开发一个像模像样的 iOS App。并且 Titanium Mobile 实现了 CommonJS 规范,可以很方便来模块化程序代码。

PhoneGap

PhoneGap 是一个使用 HTML+CSS+JavaScript 来开发移动 App 的解决方案,使用它来开发 App 只需要有 Web 开发基础即可。它在今年10月份被 Adobe 收购了,然后加入到 Apache Software Foundation 进行孵化。

目前 PhoneGap 已经支持了市面上大多数的智能手机平台,其中就有 iOS。

在我看来,PhoneGap 其实只是提供了一个运行于各种智能手机平台的浏览器的壳,通过这个壳,PhoneGap 的 JavaScript 库可以和系统进行沟通,从而实现在 Web 页面中与系统交互的功能。

因为 PhoneGap 只提供了系统功能的 API 调用,而没有提供任何和界面相关的 API,那么界面就只能自己来折腾了。

幸好开源的世界是强大的,已经有了一堆这样的界面库来供我们选择,像 jQTouchjQuery MobileSencha Touch 之类,都提供了类似于 iOS 的界面组件,可以让我们省去许多界面上的编码工作。

使用浏览器来运行 App 的一个坏处就是:慢。因为整个 App 就是一个网页,如果编码不当,整个程序使用起来体验会比较差,和原生的应用会有很大区别。

在上面提到的几个 UI 框架之中,我比较喜欢的是 jQTouch,它只提供了基本 UI 框架和一些视图切换效果,做出来的程序是一个单页面 App,在整个程序的反应速度上会感觉比较好。不过 jQTouch 也缺少一些常用的组件像对话框,而这些在 jQuery Mobile 和 Sencha Touch 之中又有提供。选择使用哪个框架就要看个人喜好了,它们的网站都提供了在线预览功能

其实,如果用不到系统的 API,只是想把自己的程序包装成一个很像 App 的 App 的话,只要用一用 PhoneGap 提供的壳就行了,它的 API 几乎可以不用看,配合一些 UI 框架可以快速的产出一个 iOS App 来。

当然了,PhoneGap 最大的好处就是,几乎跨了几乎所有的主流智能手机平台,这对于初创团队来说,是迅速推出各个平台客户端的一个好方法。另外 PhoneGap 官方网站也提供了一系列配套服务,例如使用 PhoneGap Build 可以直接编译各个平台的程序安装包,而不需要开发者自己在本地配置每个平台的编译环境。

Mono Touch

Mono Touch 是一个使用 C# 来编写跨平台应用的框架,同样支持了 iOS 和 Android 两大平台。

Mono Touch 对于 .NET 程序员来说应该是一个好消息,除了调试,其他的都可以在 Windows 中搞定,调试的时候可以通过在虚拟机里运行 Mac OS X 来解决。Mono Touch 因为是基于 Mono 的,也有个配套的 IDE 可以用:Mono Develop。因为都是 .NET,当然也可以用 Vistual Studio 这个更强大的工具了。

与 Titanium Mobile 和 PhoneGap 不同的是,Mono Touch 如果要编译到设备,或者发布到 App Store 的话,是需要收费的。

另外,因为 Mono Touch 也是编译成可执行代码再部署到设备上的,因此运行速度相对于 PhoneGap 所制作的应用来说,应该会快上一些。

不过 Mono Touch 要收费,并且收费还不是很便宜,可能会影响到它的普及率。

小结

在上面讲的三个解决方案之外,还有其他好多大大小小的移动应用开发解决方案,但是用于 iOS 上的解决方案,主要也就是映射代码、或者是包装的形式了,对于应用而不是游戏类型来说,一般都是够用的,在具体选择的时候,也可以根据自己更为熟悉哪种语言,或者框架来挑选。

如果你发现了其他有意思的移动应用开发技术,也可以和我分享一下 :)

参考资料

  1. http://www.appcelerator.com/products/titanium-mobile-application-development
  2. http://developer.appcelerator.com/apidoc/mobile/latest
  3. http://phonegap.com/about
  4. https://build.phonegap.com
  5. http://www.mono-project.com/Main_Page
  6. http://xamarin.com/monotouch
  7. https://github.com/xamarin/monotouch-samples
  8. http://blog.zhaojie.me/2010/09/develop-ios-app-with-monotouch-in-visual-studio-1.html

— EOF —

iOS App 自定义 URL Scheme 设计

在 iOS 里,程序之间都是相互隔离,目前并没有一个有效的方式来做程序间通信,幸好 iOS 程序可以很方便的注册自己的 URL Scheme,这样就可以通过打开特定 URL 的方式来传递参数给另外一个程序。

例如在 iPad 上浏览网页,并且 iPad 已经安装了 淘宝 HD,那么就打开下面这个链接就会在淘宝 HD 中查看这个商品的详细信息,也可以方便的使用淘宝 HD 进行购买、收藏等操作。

在淘宝 HD 中查看商品“2012西藏卓明谷方舟登舰卡(船票)”

当然,如果你在 Mac OS X 中打开这个链接,或者在没有安装 淘宝 HD 的 iPad 中打开这个链接,会提示没有程序来打开这个链接。

配置

要为 iOS 程序添加自定义协议的支持是一件很方便的事,只需要在程序的 Info.plist 添加一个 URL types 节点就可以了。在这个节点里,可以设置这个程序所支持的自定义协议名称,像 http、ftp 这种,一般我们可以设置为程序英文名称,像淘宝客户端中就设置了 taobao,这样 taobao:// 这个形式的 URL 就会关联到淘宝客户端的 App。

Info.plist config

实现

在 Info.plist 里面设置完 URL types 之后,就可以在程序中处理这类 URL 的打开请求了。

在外部程序中,如果打开了指定自定义协议的 URL,程序中 application delegate 的 application:handleOpenURL: 方法就会被调用,在这个方法里,可以获取到触发这个方法的 URL,可以通过对这个 URL 进行判断,例如根据不同的 Host,不同的 Query String 来执行不同的动作。

- (void)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
    NSLog(@"%@", [url absoluteString]);

    // 在 host 等于 item.taobao.com 时,说明一个宝贝详情的 url,
    // 那么就使用本地的 TBItemDetailViewController 来显示
    if ([[url host] isEqualToString:@"item.taobao.com"]) {

        // 这里只是简单地假设 url 形式为 taobao://item.taobao.com/item.htm?id=12345678
        // 先获取要查看的宝贝详情的 itemId
        NSString *itemId = [[url query] substringFromIndex:[[url query] rangeOfString:@"id="].location+3];

        // 使用本地 ViewController 来显示淘宝商品详情
        TBItemDetailViewController *controller = [[TBItemDetailViewController alloc] initWithItemId:itemId];
        [self.navigationController pushViewController:controller animated:YES];
        [controller release];
    }
}

淘宝 for iOS

现在,淘宝 和 淘宝 HD 两个客户端都支持 taobao:// 协议,来打开特定的链接。目前已经支持的有:

例如,想要在自己的程序中,使用淘宝客户端来显示一个淘宝商品的详情,以支持用户可以直接在 iPhone 上购买,收藏等,就可以使用下面的代码:

- (void)showItemInTaobao4iOS:(NSString *)itemId {
    // 构建淘宝客户端协议的 URL
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"taobao://item.taobao.com/item.htm?id=%@", itemId]];

    // 判断当前系统是否有安装淘宝客户端
    if ([[UIApplication sharedApplication] canOpenURL:url]) {
        // 如果已经安装淘宝客户端,就使用客户端打开链接
        [[UIApplication sharedApplication] openURL:url];
    } else {
        // 否则使用 Mobile Safari 或者内嵌 WebView 来显示
        url = [NSURL URLWithString:[NSString stringWithFormat:@"http://item.taobao.com/item.htm?id=%@", itemId]];
        [[UIApplication sharedApplication] openURL:url];
    }
}

使用淘宝客户端来打开淘宝链接的好处就是可以让用户更加方便的去购买商品,而不需要再重新登录,或者把用户名密码给了第三方的网站而导致安全隐患。

扩展性

在淘宝客户端中,支持的 URL 往往是淘宝网站已经有的链接,这些链接的 QueryString 中所带的参数往往已经满足了使用本地代码显示内容的需要,但是为了扩展性考虑,就需要添加一些额外的参数,并且与原有 QueryString 中不冲突的参数名称。通过这些额外的参数,再实现客户端打开链接时更多的自定义行为。

例如,如果在打开特定 URL,进行一些操作后需要再返回原来的程序,就会需要在 URL 中添加类似于 callback 这样的参数,这样在客户端处理完用户的操作后,可以将用户操作的结果返回给原来的程序,从而实现程序间的通信。

示例:

- (void)buyItemInTaobao4iOS:(NSString *)itemId {
    // 构建淘宝客户端协议的 URL
    NSString *format = @"taobao://item.taobao.com/item.htm?id=%@&_action=buy&_callback=myapp://taobaobuysuccess";
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:format, itemId]];

    // 使用淘宝客户端打开链接
    [[UIApplication sharedApplication] openURL:url];
}

注意:当前淘宝客户端并不支持这样的调用方式,这里仅是一个示例。

当然,在使用这种方式实现程序间通信的时候,需要考虑检查一下来源 URL 的合法性,防止一些非法的调用造成用户的损失。

结语

通过自定义协议地支持,可以将 iOS 程序的一些功能和服务提供给外部程序,也可以实现 Web 和本地应用之间的互相调用。

如果你的 iOS 程序有这些需求的话,那么就可以考虑在 iOS 程序中添加自定义协议的支持了。

参考资料

  1. iOS Application Programming Guide: Implementing Custom URL Schemes

— EOF —

淘宝 for iOS 历程

这是在 [淘宝2011技术嘉年华](http://developerclub.taobao.com/) 上讲的关于淘宝 for iOS 开发过程的演示文稿。

View more presentations from ohdarling88.

— EOF —

给 iOS 项目添加自动更新属性

在 iOS 项目中,有些时候为了方便跟踪发布时的版本,方便调试,或者为了展示程序的编译日期,可以通过在 Info.plist 里面添加自定义属性的方式来实现,但是这个工作是一个纯粹机械的重复劳动,每次手工去更新是比较麻烦的一个事情。

好在 Xcode 项目支持自定义脚本的编译步骤,我们可以通过在编译过程中使用自定义脚本去更新 Info.plist。

在 /usr/libexec 目录下面,有一个工具 PlistBuddy,它可以很方便地修改 plist 文件,而不需要去用 sed 之类的编辑工具来修改。

为了在使用 PlistBuddy 的时候可以直接用 Set 命令设置属性,我们先在 iOS 项目的 Info.plist 里面加一个需要自动更新的属性,例如 BUILD_DATE 和 GIT_REVISION,类型选择为字符串,值填 AUTO_GENERATED。

然后在 iOS 项目的 Target 上添加一个新的编译步骤 Run Script Build Phase:

下页就是要编写脚本了

# 获取编译的日期
DATE=`date +%Y.%m.%d`
 
# 获取编译时 Git 库的短版本号
GIT_REVISION=`git rev-parse --short HEAD`
 
# 设置 Info.plist 中相关的属性
/usr/libexec/PlistBuddy "$TARGET_BUILD_DIR/$INFOPLIST_PATH" -c "Set BUILD_DATE $DATE"
/usr/libexec/PlistBuddy "$TARGET_BUILD_DIR/$INFOPLIST_PATH" -c "Set GIT_REVISION $GIT_REVISION"
 
# 将 Info.plist 转换为二进制格式
plutil -convert binary1 "$TARGET_BUILD_DIR/$INFOPLIST_PATH"

如果需要在程序里获取这些属性,那么就可以用下面的方法:

NSString *buildDate = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"BUILD_DATE"];
NSString *revision = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"GIT_REVISION"];

— EOF —