iOS

Image of "Download on the App Store"

之前做 App 的下载页面时,都是只需要简体中文就够了,所以“从 App Store 下载”的图片是在网上随便找了一个用的。这次还需要繁体中文和英文的图片,刚想搜索一下,突然意识到这种图片苹果官方肯定是提供的。

于是很轻松就找到了 App Store Marketing Guidelines,里面给出了所有可能用到的图片资源,包括所有语言的 Download on the App Store 按钮图片、所有苹果设备的图片等。以及在宣传品中使用这些图片时的规范要求。

The new Media Manager on iTunes Connect

收到一封来自苹果的邮件,说是最新的 Media Manager 已经上线 iTunes Connect 了。

管理屏幕截图差不多是 iTunes Connect 里最麻烦的。最初只有一两种屏幕尺寸时还好,现在 iPhone 有四种尺寸,iPad 有两种尺寸,如果再需要支持多种语言,那上传图片的人基本就要疯了。比如词记提供 4 张截图,每种设备都有对应的截图,目前支持 3 种语言,那么总共就有 4 × (4+2) × 3 = 72 张图片。截图、压缩、上传,还是很磨性子的。相信我,这个数量不算多。

UIActivityViewController

做社会化分享,经典的做法就是在各个社会化平台申请 App Key,然后集成各个平台的 SDK,并分别进行开发。更简单的方式是用类似友盟分享 SDK、ShareSDK 之类的集成化 SDK,让多平台分享的开发更加容易。之前我和团队就是采用这种方式。

其实 iOS 本身提供了一种分享的方式,App Store 里的分享就是一个实现的例子:
UIActivityViewController

这个弹出的分享框,叫做 UIActivityViewController。通过它来实现分享,好处是样式统一、实现简单,更重要的是它直接使用系统中设置的平台账号,并且不需要对应用进行授权。这一点降低了用户分享的心理负担。同时,开发者不需要去平台为应用申请 App Key,也一定程度上降低了开发的门槛。

应用上线后 IAP 商品不会立即生效

应用和 IAP 项目均在 App Store 审核通过后,发现从 App Store 内下载的版本,无法正常获取到 IAP 商品信息,通过 product ID 获取 product 时,未能返回相应的 product。

测试环境(Sandbox)下一切正常,可根据 product ID 获取到 product,完成支付。

推测应该是由于 IAP 审核通过后并没有同步及时的上线,所以导致无法正常获取到 product,等待一段时间即可。(我等待了约 7 个小时就正常了,也有人等待了一两天)

所以在程序中要注意,如果获取不到 product 做相应的处理,防止程序崩溃。

iTunes Connect Error ITMS-90475

提交 binary 到 iTunes Connect 时,遇到了之前未遇到过的错误:

ERROR ITMS-90475: “Invalid Bundle. iPad Multitasking support requires launch story board in bundle ‘org.stoneark.worddiary’.”

原因是,iPad 程序(或者 Universal 程序),从 iOS 9 / Xcode 7 开始,默认要求支持多任务分屏。但这要求必须使用 Launch Storyboard,而不能再用 Launch Image。而正如之前说过的,所以我并没有采用 Launch Storyboard,所以导致了这个问题。

目前可以暂时不支持多任务分屏,在工程属性的 General 里,把 Requires full screen 勾选上即可(会在 Info.plist 里增加字段 UIRequiresFullScreen)。

Application localization in iOS development

iOS 对国际化的支持是相对比较完善的。

应用的国际化支持

在 Project 属性中,有 Localizations 一项,在其中可以管理支持的语言列表,还可以选择是否开启 Base 语言。

为资源文件开启国际化

在资源文件(如 strings/xib/storyboard)的文件属性中,可以在 Localization 项中进行国际化语言的管理。如果尚未国际化,会有一个长长的 Localization 按钮,点击后开启这个文件的国际化。如果已国际化,会显示并可管理当前文件已支持的语言。

如果想取消对这个文件的国际化,在 Xcode 里做不到。唯一的办法是,去 Finder 里把各个语言的 .lproj 目录下的这个资源文件删掉,只留一份放到工程目录的最外层(而不从属与任何 .lproj 目录)。

字符串的国际化:

自己新建一个 String 文件到工程里,名为 Localizable.strings 即可。(这是默认的文件名,调用 NSLocalizedString 方法默认取这个文件,如果用了其他的文件名,则每次调用方法时,需要指定文件名)

例如,Localizable.strings 内容为:

Localizable.strings(Chinese (Simplified))
1
2
3
4
5
"Today" = "今天";
"Yesterday" = "昨天";
"%d days ago" = "%d天前";
/* {User First Name}'s Profile */
"%@'s Profile"="%@'s Profile";
Localizable.strings(English)
1
2
3
4
5
"Today" = "Today";
"Yesterday" = "Yesterday";
"%d days ago" = "%d days ago";
/* {User First Name}'s Profile */
"%@'s Profile"="%@的个人资料";

获取时:

1
2
3
4
5
NSString *str;
str = NSLocalizedString(@"Today", nil);
int temp = 3;
str = [NSString stringWithFormat:NSLocalizedString(@"%d days ago", nil), temp];
str = [NSString stringWithFormat:NSLocalizedString(@"%@'s Profile", @"{User First Name}'s Profile"), user.name];

上面是常用的情况。还有一种复杂的情况,是在有些语言里,某两个参数的位置可能会颠倒,这时需要使用转义符:

1
"%@ Error! %@ failed!" = "%2$@ 失败了!%1$@ 错误了!";

更为详细的可参照:
http://nshipster.com/nslocalizedstring/
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/LoadingResources/Strings/Strings.html
https://developer.apple.com/library/ios/documentation/MacOSX/Conceptual/BPInternational/InternationalizingYourCode/InternationalizingYourCode.html

xib/storyboard 的国际化

除了借助 localizable strings 文件进行界面国际化之外,对于 xib/storyboard 可以直接进行国际化。虽然看上去很方便、很直观,但其实并不是特别好用。

它的原理其实还是 localizable strings,只不过这个 strings 文件是 Xcode 自动创建的。创建的时机是在你对这个界面开启 localization 时,而且它并不会监控 xib/storyboard 的变化。如果你在 localization 之后又改变了这个 xib/storyboard,主要是增加了新的控件,那么这个新控件并不会自动出现在 strings 文件中。

那你就没办法了,Xcode 也没有提供同步的手段,你唯一能做的是查看 xib 的源代码,从中找到新建控件的 ID,然后自己写到那个 strings 文件里--噢,这一点都不方便。

Bundle Display Name 的国际化

显然,需要对不同语言设定不同的应用名称,也就是 Bundle Display Name,我们都知道它在 Info.plist 里。

但注意,不是把 Info.plist 进行 Localization,如果对 Info.plist 进行了 Localization,会编译报错 the file Info.plist does not exist.

正确的方式是在工程里新建一个名为 InfoPlist.strings 的文件,然后对这个 strings 文件进行国际化,在里面写入:

1
"CFBundleDisplayName" = "名称";

即可。

注意,需要把 InfoPlist.strings 文件加到工程属性里的 Copy Bundle Resources 里(默认应该就给加上了,如果有问题可以检查一下)。但不要把 Info.plist 文件加到 Copy Bundle Resources 里,否则出现编译警告。

Base 语言的问题

测试发现,将 iOS 设备设置为并没有支持的语言时,应用并没有正确地显示为 Base 中的字符串(英语),而是显示成了简体中文。

Stackoverflow 上有很多国内外开发者遇到了这个问题,但都未找到原因和解决方法。

http://stackoverflow.com/questions/20241256/ios-app-default-language-en-is-not-applied
http://stackoverflow.com/questions/20584984/ios-set-a-default-language-in-xcode-for-my-app
http://stackoverflow.com/questions/18114994/does-my-base-internationalization-storyboard-have-to-correspond-to-a-fallback-la

App Store 应用名称及简介的国际化

希望让应用在不同国家的 App Store 中显示不同语言的名称,在 iTunes Connect 里新建应用时先用一个主要语言新建,如简体中文,新建成功后在 App 信息里可以看到“可本地化的信息”,可以在右侧选择语言,并且填入相对应的信息。另外,在版本中对应用简介、关键字、截图等也都须进行国际化。

如果未做本地化,则按照选择的主要语言显示。

Bug about launchscreen storyboard

Launchscreen storyboard,看起来很美,用起来很臭虫。

之前一直没在实践中真正采用过它,这次一试,可真是让人摸不着头脑。

一旦有两个 ImageView,则它们的表现会非常奇怪。尤其是两个图像有重叠部分的时候。
即使一张图片在另一张下方,也会把上方图片的重叠部分遮盖住。

设计时:(灰色渐变背景在底层,词记图标在上层)

运行时:(重叠部分被底层遮盖了)

关于 Launchscreen storyboard,涉及到 UIImageView 出现了很多问题,至今也没有解决:
https://forums.developer.apple.com/thread/17146
https://forums.developer.apple.com/message/62721

暂时还是不用它了,用回 Launch image asset。

Local notification changes after iOS 8

大多数 iOS 开发者应该知道,在 iOS 8 的时候推送通知的开发有了些变化。主要体现在一些命名的变化(Remote 改为 User),另外相对之前只需要一句代码就能解决,现在需要多一些步骤。但总的来说改变并不大,实现原理并未改变。

如 iOS 7:

iOS 7
1
2
3
4
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(
UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound )];

iOS 8:

iOS 8
1
2
3
4
UIUserNotificationType notificationTypes = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:notificationTypes categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
[[UIApplication sharedApplication] registerForRemoteNotifications];

虽然很多人早就已经知道并在实践中作出了改变,但可能依然有些人并不知道 iOS 做出这种改变的原因。

原因是要对本地通知进行规范,并与远程推送通知进行统一。

之前对于本地通知,是无需取得用户授权的,只有远程推送类型的通知需要申请用户授权。上面的那些代码也只是针对远程通知来说的(所以它们之前的命名中都带有”Remote”字眼),本地通知根本无须注册,直接发送通知就好了。但其实这并不合理。

进行改变之后,本地通知跟远程推送通知的概念进行了统一,它们唯一的不同只是消息来源的不同。所以本地通知也需要进行授权。

在代码上,它们的注册也只有一句代码的差距:如果只需要本地通知,则不书写最后的一句:

1
[[UIApplication sharedApplication] registerForRemoteNotifications];

即可。

iCloud development

iCloud 开发的几种模式:

  1. Key-value Storage,在 iCloud 上保存一些简单数据,类似 NSUserDefault 的 iCloud 端实现。每用户最多 1MB 存储。
  2. iCloud Document,在 iCloud Drive 上以文件方式保存的数据。
  3. CloudKit,后端云存储。与上面两个不同,上面是用户自己的 iCloud 存储,只有用户自己可以读取自己的数据。而 CloudKit 是开发者的 iCloud,是与用户无关的,所有用户都可访问。类似 LeanCloud、Bmob 之类的服务。目前可以认为对开发者是免费的(因为免费额度绝对足够大),除了 iOS 外还提供了 JS 的访问方式。

具体可参考 About Incorporating iCloud into Your App

Swift 与 Objective-C 混编

Swift 这门新兴的编程语言,自从 WWDC 2014 被发布以来不断被完善,并获得了越来越多苹果开发者的认可。WWDC 2015 时 2.0 版本的发布以及开源,让我们看到了 Swift 的不断成熟,苹果也在提倡开发者们向 Swift 转移。

我很早就阅读了 Swift 的相关内容,并且感觉它是一个很不错的开发语言。它具有一些现代脚本语言的特征,更重要的是跟我早先常用的 Pascal 语言在很多地方十分相近,感觉很亲切。虽然如此,实际在工作中还并未引进 Swift,我和我的团队依然在用 Objective-C 进行 iOS 软件的开发。

今天突然想在开发新功能时试一把 Swift,于是研究了一下 Swift 和 Objective-C 的混编,在原有 Objective-C 工程中引入了 Swift。

Swift 代码调用 Objective-C 代码

在现有 Objective-C 工程中,新建一个 Cocoa Touch Class,新建时选择 Language 为 Swift,会弹出对话框提示是否需要创建 Bridging Header:
Create Bridging Header

创建后工程中会新建一个名为 ProjectName-Bridging-Header.h 的头文件。编辑这个头文件,Swift 代码需要调用的 Objective-C 头文件,都在这个文件中 import 即可:

ProjectName-Bridging-Header.h
1
2
3
4
5
6
7
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "BaseViewController.h"
#import "APIHandler.h"
#import <YYModel.h>
#import <MJRefresh.h>

另外,可以在工程属性的 Build Settings 中,可以找到 Objective-C Bridging Header 项,其中已自动填入了这个 ProjectName-Bridging-Header.h 的路径。

Objective-C 代码调用 Swift 代码

在需要调用 Swift 代码的 Objective-C 文件头部,写入:

1
#import “ProductModuleName-Swift.h”

即可,然后就能调用工程中 所有 的 Swift 源码,并不需要单独指定需要调用哪一个。
这个 ProductModuleName,默认就是工程名称,如”YouYouYuEr-Swift.h”。可以在工程属性的 Build Settings 中,找到 Product Module Name 项查看。

这个头文件的全名,也可在工程属性的 Build Settings 中,找到 Objective-C Generated Interface Header Name 项查看,可以看到它的值为 $(SWIFT_MODULE_NAME)-Swift.h。(可见上一小节第 2 张图)

但注意,这个文件在工程中 并不 实际存在。因为就像刚才所说,import 这个文件后能调用工程中 所有 的 swift 源码,所以就不需要由开发者再去编辑这个文件,来告知编译器需要引用哪些 .swift 文件。而反过来则不同,Swift 调用 Objective-C 需要告知编译器需要调用哪些 Obejctive-C 文件,所以需要一个 ProjectName-Bridging-Header.h 供开发者去编辑。

总结

可以看到,Build Settings 中的 Objective-C Bridging HeaderObjective-C Generated Interface Header Name 两项,是 Swift 与 Objective-C 混编的关键,分别为一种语言引用另一种语言提供了桥梁。