iOS 9 开发高级技巧
为了庆祝 iOS 9 的正式发布,我们咨询了一些朋友,让他们分享一下在适配最新版本 iOS 的过程中所需注意的一些事情。下文列出的就是如何加快速度适配 iOS 9 的绝妙建议。
Dave Verwer
@daveverwer — Curated创立者,目前正在维护iOS Dev Weekly
很难得的是,iOS SDK 中竟然有个新 API 可以有效帮助应用展示和推销自己,因此对我来说,iOS 9中最令人激动的新特性就是搜索 API 了。
这些功能是在NSUserActivity的基础上构建而成的,NSUserActivity在 iOS 8 中为了支持 Handoff 而引进。向应用中加入 iOS 9 提供的额外元数据(metadata)以及深度 URL 连接,便可以让应用的信息出现在 iOS 9 的 spotlight 搜索队列当中,不仅可以给用户展示应用的名称,还可以让用户探索应用中的内容。
这项功能对您现有的用户带来的益处是显而易见的,不仅如此,苹果还会对这些搜索结果进行排序。当没有安装您应用的用户使用 spotlight 的时候,您的应用同样也会出现在建议列表当中。这就是所谓的免费营销?好吧,或许是的。
这项功能最有意思的一点是,苹果只会给新用户推荐您的应用;对于老用户来说,他们将会正常使用您所提供的搜索结果。它将会立即摒弃无用的应用,只显示那些真正提供实际信息的应用,这对 App Store 来说无疑是一件非常棒的事情。
虽然关于搜索 API 的内容还可大书特书,然而对于“小技巧”来说再说下去无疑是浪费篇幅了。因此,如果您对此项功能感兴趣的话,您可以在 WWDC 上观看Session 709 — Introducing Search APIs。此外,有一个非常好用的工具用来验证您的网站,以确保 iOS 能够正常地显示您的应用链接。
</div>
Tim Oliver
@TimOliverAU — iComics创始人
随着 iPhone 6s 以及 iPhone 6s Plus 的发布,开发者们现在就可以为自己的应用配备上 3D Touch 功能了,从而给界面交互方式开启一个新的维度。
正如苹果所言,开发者可以通过非常简单的 API 来使用 3D Touch ,从根本上来说,也就是UITouch的一个简单的新属性。
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { guard let touch = touches.first else { return } if traitCollection.forceTouchCapability == .Available { println("Touch pressure is \(touch.force), maximum possible force is \(touch.maximumPossibleForce)") } }
这个新的 API 可以让应用发挥出巨大的潜力,比如说游戏中的额外控制选项、绘图应用中的细粒度(fine-grained)控制,甚至是用来替代我们在 iOS 设备中使用过的长按操作(tap-and-hold)的极佳选择。
除了 UITouch 中新增的 API 外,苹果还为应用提供了两个用来增加3D Touch 功能的类集:UIPreviewAction和UIApplicationShortcutItem。
UIPreviewAction允许开发者在用户使用 3D Touch 功能触控一个 UI 元素的时候,快速地在一个新的预览窗口中显示某些内容。这种快速浏览应用特定内容的方式真的非常棒,比如说我们可以快速预览邮件信息、照片,甚至是网页内容,而无需弹出一个完整的视图控制器。
UIApplicationShortcutItem对象能够让 iOS 主屏幕激活一项令人惊叹的新特性。当用户使用 3D Touch 按下某个应用的图标时,一个选项列表就会被弹出,允许用户快速跳转至应用的特定部分,或者执行某项应用内的功能。
总而言之,3D Touch 的引入给 iOS 设备解锁了一个全新的交互方式,并且将会给接下来的 iOS 应用带来新一代的创新。关于3D Touch 的实例代码和相关信息可以在苹果开发者网站的3D Touch网页上找到,祝你好运!
iOS 9.0 和 OS X 10.11 分别新引进了UILayoutGuide和NSLayoutGuide这两个类。它们允许您创建一个“类似视图”的对象,用来参与自动布局约束的计算而无需在屏幕上创建多余的视图。比如说,您可以使用这些新的类来作为占位图,而不是创建一个空白的视图来进行占位。
// 创建Layout Guide let layoutGuideA = UILayoutGuide() let layoutGuideB = UILayoutGuide() // 将它们添加到视图上 let view: UIView = ... view.addLayoutGuide(layoutGuideA) view.addLayoutGuide(layoutGuideB) // 使用它们添加约束 layoutGuideA.heightAnchor.constraintEqualToAnchor(layoutGuideB.heightAnchor).active = true // 您甚至可以设置它们的标识符... layoutGuideA.identifier = "layoutGuideA" layoutGuideB.identifier = "layoutGuideB" // 然后使用它们可以得到计算后的视图尺寸(只有当拥有 Layout Guide 的视图出现之后才有效) print("layoutGuideA.layoutFrame -> \(layoutGuideA.layoutFrame)")
Indragie Karunaratne
@indragie — Mac、iOS 软件工程师,学生
在 iOS 9 中引入的NSLayoutAnchorAPI 不仅让约束声明更加清晰明了,而且还通过静态类型检查以确保您的约束保证能够正常工作。比如说,我们有一个约束,是使用旧有的NSLayoutConstraintAPI 进行创建的。
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0];
这个约束是无效的,因为我们为 X 轴(左侧)属性以及 Y 轴(顶部)属性建立了一个相等的约束。然而,这种做法不会得到任何警告,它能够继续运行,然后_悄悄_地崩溃掉,让您的约束处于一个未定义的状态,接着留下一个棘手的烂摊子让您来处理,没有任何的记录能够提醒您,以便让您确认在几十个(甚至数百、上千)约束中哪一个出现了问题。
NSLayoutAnchor通过使用在 Swift 和 Objective-C 中存在的泛型特性很好地解决了这个问题。UIView上的锚点访问器(anchor accessors) 能够显示为继承自NSLayoutAnchor方法添加类型信息的NSLayoutAnchor的子类。对于 X 轴、Y 轴以及尺寸(宽、高) 锚点来说,都有着不同的NSLayoutAnchor子类,因为某个类型的锚点只能够被与之相同类型的其他锚点所约束。通过NSLayoutAnchor中的方法来约束锚点参数以及作为接收器的相同泛型类型,API 变能够使用类型检查以确保能够创建出有效的约束。
让我们再回到前面的例子来,这里是一个使用NSLayoutAnchorAPI 的等价约束声明:
NSLayoutConstraint *constraint = [view1.leadingAnchor constraintEqualToAnchor:view2.topAnchor];
这不仅比旧有的 API 更加通俗易懂,并且这也会弹出一个“不兼容的指针类型”(Incompatible pointer type)的编译警告,因为编译器知道您不能够创建两个不同锚点类型的约束。
要了解更多信息,请参阅NSLayoutAnchor documentation。
有关 Swift 和 Objective-C 泛型的注意事项:在写这篇文章的时候,笔者正使用 Xcode 7 beta 6 版本,Objective-C 的泛型并不能够很好的与 Swift 建立桥接。这意味着在这个提示中所提及的额外类型安全在 Swift 中并不能使用,但是却能够很好地在 Objective-C 中应用,正如Joe Groff所提到的那样。
Ayaka Nonaka
@ayanonagon — [Venmo] iOS 工程师(https://venmo.com)
关于 iOS 9 我给出的技巧就是抛弃 iOS 7,开始使用诸如UIAlertController (iOS 8+) 此类的新 API,我喜欢使用这个 API 的原因在于它在显示警告框、下拉列表以及UIStackView (iOS 9+)的时候会强制只显示唯一一个视图控制器,这可以让我们以一种理智的方式考虑布局。说真的,如果您还没有试过的话那么我推荐您去试试,它真的非常好用。如果您想要用回原来的 iOS 版本的弹出框,那么您可以使用一些开源库(PSTAlertController and TZStackView)。因此,一旦您准备抛弃 iOS 7 或者 iOS 8 的时候,那么就使用这个新功能来替换原来的版本吧!
</div>
Conrad Kramer
@conradev — [Workflow]创立者(https://my.workflow.is/)
苹果在 iOS 9 中引入了应用传输安全功能,它默认需要所有的应用使用 HTTPS 协议。由于不是所有的服务都由 HTTPS 提供,因此苹果还提供了一个禁用 ATS 的方式,既可选择性的使用也可全局应用。
如果您的应用需要能够加载所有的 URL(比如说在 UIWebview中),那么您可能需要通过设置NSAllowsArbitraryLoads键值为YES来全局禁用 ATS。这完全是可以的,但是一旦您全局禁用了 ATS 功能,那么您需要在重要区域启用 ATS 服务。您需要使用NSExceptionDomains键来完成此项功能。比如说,这是 Workflow 的 Info.plist 文件的一部分内容:
您可以看到,我们支持用户通过 HTTP 下载文件,但是我们同样也支持通过 HTTPS 来连接 workflow.is(以及所有 Workflow 使用的 API )。
还需要注意的是,每一个包都需要应用 ATS 功能。这意味着您不仅需要给您主应用的 Info.plist 文件添加 ATS 字典,而且还要同时给扩展的 Info.plist 文件添加。
</div>
Jake Marsh
@jakemarsh — Little Bites of Cocoa创立者
竭尽所能地实现新的搜索功能!竭尽所能地实现新的搜索功能!竭尽所能地实现新的搜索功能!重要的事情说三遍。搜索功能确实是一项重大的进步,iOS 9 仅仅只是个开始。我建议尽快让您的应用支持NSUserActivity。这个操作是相当地简单,当您完成之后,您会发现您的应用将同时支持 Handoff 以及 新的系统。如果您的应用还可以有任何形式的搜索内容,那么您一定要 体验新的 Core Spotlight 框架的优势并且告诉系统如何对其建立索引。您还应当确保和您应用相关联的所有网页内容都针对 iOS 9 的新搜索视图进行了优化,苹果对此发布了一个绝佳的指南,指导如何使用一个简单的meta标签来完成诸多的功能。
系统对用户所做所为了解得越多,它所提供给用户的建议和选项也就更加智能,更加贴合用户。
</div>
Sam Ritchie
@FakeSamRitchie — codesplice首席CodeSplicer
每个在共享的 iOS 代码平台上工作的人在面对 storyboard 文件合并冲突的时候都会抓狂,这往往导致您不得不在 IB 中手动重制 storyboard 中的更改。这也是许多团队放弃使用 storyboard 开发并且在source control shingle上提出诸多问题的原因。
如果您没有任何好用的处理工具的话,那么很不幸,减少合并冲突的最好办法是将您的 UI 分解成更小的 storyboard 文件。在过去这意味着您需要在代码中实现导航栏,这样才能够跨 storyboard 使用。但是在 Xcode 7 以及 iOS 9 当中,您能够通过一个名为 Storyboard Reference 的普通 segue 来完成此项功能。它能带来如同单一 storyboard 导航栏一样的便利,并且还允许您将文件切割成小份以减少合并冲突。
分离硕大的 storyboard 最快也是最简单的方法就是将 storyboard 界面放大,然后按下 Shift 键选择要分离并且相关联的场景,然后选择菜单栏的 ‘Editor > Refactor to Storyboard’ (没错,storyboard 在 Swift 出现之前就支持重构了)即可。然而,这种做法会导致每个被分离的场景仍会在原 storyboard 中留下其引用,并且在必要的时候会自动产生非常难看的 storyboard ID,因此我个人更倾向于使用 ‘File > Duplicate…‘,然后删除多余的场景即可。
如果您已经拥有了多个 storyboard,那么为自己欢呼一下吧——现在只需要删除代码就好了!从对象库(Object Library)中拖入一个 Storyboard Reference,然后配置一下 segue,接着重重的按下删除键,将导航栏代码删除,大功告成!
</div>
Natasha Murashev
@NatashaTheRobot — Capital One iOS 工程师, Natasha The Robot博主
我并没有特别关注 iOS 9 的相关内容,不过我倒是在 watchOS 2 上进行的开发工作。如果您拥有一个 Apple Watch 应用的话,那么我建议您应当使用全新的 Watch Connectivity 框架对应用从头开始进行重写。WatchOS 2 与原先发布的 WatchKit 扩展完全不同,并且功能更加强大。WatchOS 2 代表着未来,因此仍然维护 WatchKit 扩展无疑只是徒增困扰而已。
此外,如果您目前在 Apple Watch 上使用的是 Notifications、Glances 的话,那么我推荐向您的 Watch 应用中添加 Complication 组件。将来的 #1 (也可能是即将到来的 #1)趋势正是 Complication,试想当用户每次抬起手腕之后,您的应用就能够正好出现,那该是多么美妙的用户体验!
要了解更多内容,您可以查看以下资源:
- WWDC 2015 Video: Introducing Watch Connectivity
- WWDC 2015 Video: Creating Complications with ClockKit
- watchOS 2 Tutorials from Kristina Thai
- Getting Data to Your watchOS 2 App
Riley Testut
@rileytestut — GBA4iOS创立者, 就读于南加利福尼亚大学
如果您和我一样,让代码尽早跳出以便更好地进行逻辑、数据内容的确认,虽然 Swift 自发布的第一天起就让代码提早跳出变得可能,但是它仍然还有一些需要注意的地方。首先,您需要检查不需要的条件(比如说变量为空),而不是检查您需要的条件。更重要的是,绝大多数情况下,当变量为空的时候,您往往想要让代码跳出,然后如果变量不为空的时候就继续运行,但是接下来如果您想要在剩余的代码中使用此变量的话,就需要对变量进行手动拆包,。
在 Swift 2当中,Swift 团队给我们提供了一个完美的关键词:guard来帮助我们提早跳出代码。guard同时修复了上面提出的两个问题。试想,您正在玩一个游戏,由于开发者懒得提供不同的回调,因此所有的输入变化都在一个回调函数中进行处理:
func gameController(gameController: GameControllerType, didActivateInput input: InputType?)
在输入无效的时候使用二次回调似乎更加有效,但是这反而证明了 guard 的极佳用处。如果可选的input是非空值,那么表示某个按钮被按下了,那么游戏会继续进行。而如果按钮不再被按下,那么input就会为空。如果我们只关注于按钮被按下的情况,我们无需使用如下所示的 Swift 1中所使用的提早退出机制:
func gameController(gameController: GameControllerType, didActivateInput input: InputType?) { if input == nil { return } self.performExampleMethodWithNonOptionalInput(input!) }
注意到,我们将输入和我们不想处理的情况进行了比较,这里是“输入为空”的情况。更重要的是,当前的输入值仍然还是一个可选值,因此之后在函数中的使用我们都必须要使用强制解包,即使我们已经 知道它是非空值。在 Swift 2 中,就变得轻松多了:
func gameController(gameController: GameControllerType, didActivateInput input: InputType?) { guard let unwrappedInput = input else { return } // 在实际开发中,我往往使用和原变量相同的名字来解包, // 然而,unwrappedInput 这种命名方式要更为清晰一些。 self.performExampleMethodWithNonOptionalInput(unwrappedInput) }
在这里,我们“监视”着input,一旦其为非空值就将其存入到unwrappedInput当中,否则的话就退出方法。现在我们就能够使用非空值的unwrappedInput了,皆大欢喜!因此,guard 能够帮助 iOS 9 代码变得更加清晰易懂,减少错误的发生。
Janie Clayton
@RedQueenCoder — iOS/Mac 开发者, Red Queen Coder博主, NSBrief管理员
我的项目和绝大多数人的有一些小小的不同,我编写的是 Mac 上的机器人控制程序。面临 iOS 9、Swift 2以及 El Capitan 的发布,我们已经做好了准备,其中最重要的一点就是 Swift 2 中新出现的错误处理(Error Handling)机制了。当 Swift 1发布之后,我们自行编写了自己的错误处理方法,因为在使用 NSError 的时候,我们面临了许许多多的问题。
由于我们要和硬件打交道,因此错误处理对于我们的软件来说非常重要。因为如果您不能恰当地处理错误或者先于错误发生前做点什么,那么很有可能会对硬 件造成极大的伤害,这个错误的代价就十分地昂贵了!因此,如果要让我提供关于 iOS 9 的小技巧的话,我想说的是如果您在使用 Swift,那么请尽快掌握错误处理。虽然我知道这货和单元测试一样,属于“我们知道应当做,但是它太无趣了而且好像做不做都没啥关系”一类,但是我建 议,我们并不能控制外界因素所造成的错误,因此想想一旦错误发生之后,您的应用应当如何应对。
Glen Low
@pixelglow — Instaviz创始人, Apple Design Award 2004年度获胜者
为了领悟 UIKit 编程之道,您不应当墨守成规、坚守老旧系统,而是应当追随 UIKit 框架更新的脚步,不然的话,每当苹果发布一次新的 iOS 版本,您就会发现苹果渐行渐远,而您已无力追随。
抓住一点:UIKit 的目的在于让您目前所展示的视图控制器“切实”地显示在屏幕上。比如说,对于 Popover 弹出视图,无论屏幕的尺寸和方向如何,它都能够重新调整结点位置,自行适应。
开发者可以在 UIAadptivePresentationControllerDelegate 或者 UIPopoverPresentationControllerDelegate 中,获取系统所适应的显示默认值以及提供一个新的弹出锚点。在屏幕尺寸或者尺寸级别(Size Class)改变的过程中千万不要移除您的视图控制器。此外,这两个委托只能够提供一些简单的自定义功能:当屏幕在自适应的时候,可以很方便地用一个新的 视图控制器替换掉当前视图控制器,但是实际改变现有的视图控制器确实一件相当困难的事情,比如说禁用返回按钮。
如果不遵守这些约定的话,在旋转设备、执行 iPad 多任务或者在最新的新功能当中,您的应用轻则会表现出诡异的行为,重则会导致崩溃,从而让您在漫漫长夜中不得不熬夜苦战,解决 BUG。
</div>感谢您的阅读!现在就行动起来,把这些激动人心的新功能用在您的应用上面吧!