本文最初发布于Medium网站,原作者Jordan Morgan。本文经授权由InfoQ中文站翻译并分享。
我们每个人都应该听说了,Apple发布了iPhone X。随之而来的,是新推出的自动隐藏在手机屏幕底部的一个横条,官方称其为“Home指示键”。它唤起了用户对iPhone物理Home按钮的怀旧感。
对于消费者而言,这意味着在硬件和软件上的一个新奇迹,每次预定时需要投入更多的钱。但是对于很多开发人员而言,这意味着应如何去处理这个鬼东西。感谢上帝,答案非常简单。
本周,我们将介绍Apple已为我们给出了的Home指示键处理技术。
首先
并非每天都有新视频随硬件发布,但这恰恰是在此之后发生的事情:
在“为iPhone X设计”的活动中,苹果的终身设计大师Mike Stern制定了一些基本规则。一碗水端平,在使用下面的新功能之前,希望你首先踩住刹车,看一下自身情况是否符合如下规定:
尽量避免在Home指示键附近做交互控制,尤其是通过手势识别驱动的控制。
不能隐藏Home指示键,也不能在四周做装饰,或是尝试去更改其外观。这些规则同样适用于位于iPhone顶部的相机面板(Bezel)
通常除非观看体验(即视频、照片幻灯放映等)很差,否则通常不应隐藏Home指示键。
原文太长了,读不下去了!。反正Apple的意思就是在大部分情况下,我们都不要去骚扰那个可怜的Home指示键。
但是,本文介绍的正是其中的特例。
添加UIViewController
无论你是否喜欢根据每个控制器处理状态条,或者你纯粹是对Home指示键看不惯,Apple依然继续延续了因人而异的决策方式,而非选择去满足大众的设计。
隐藏Home Indicator的机制在本质上类似于状态条的处理:
class ViewController: UIViewController { override func prefersHomeIndicatorAutoHidden() -> Bool { return true } }
我们在前面说过,此类场景是个别情况,因此如上实现缺省返回False值。但在文档中特别提及:
系统也考虑到了个人喜好,返回YES并非确保会去隐藏Home指示键。
文档中似乎并未提及,为什么或是什么时候UIKit不会遵循开发人员选择的偏好。文档认为,在Apple看来是最好的方式,它就会强制执行这一方式,无论程序返回的布尔值是什么。对此,肯定会有一些Stack Overflow帖子讨论这一问题。
此外,这一问题看上去十分明显,但可能是开始产生混淆的一个源头。值得特别注意的是,函数名结尾是autoHidden但是并未隐藏,其实只能说明函数返回True意味着UIKit只有在其准备好之后才会去隐藏Home指示键(正常情况下,如果控制器在数秒时间范围内并未接受到任何触摸事件),而不是立刻去隐藏。
UIKit的信号处理
我们看一下类似的状态条API。我们并不能仅是重写API,或是将API赋予一个有条件地控制重写函数的变量。我们可使用另一个新的添加到视图控制器的稳健函数族,setNeedsSomethingDone:
class ViewController: UIViewController { var shouldHideHomeIndicator = false override func prefersHomeIndicatorAutoHidden() -> Bool { return shouldHideHomeIndicator } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.shouldHideHomeIndicator = true self.setNeedsUpdateOfHomeIndicatorAutoHidden() } }
它可用于直接分配(Pass Through)函数,因为它只是向UIKit发出信号,告知UIKit我们已经更改了前面选择用于Home指示键可见性的值。不同于状态栏的是,从技术上看它并非立刻产生动画效果(Animatable),因为UIKit是根据自身约定执行隐藏动作。所以,下面的代码并不会产生任何效果:
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { self.shouldHideHomeIndicator = true UIView.animate(withDuration: 1, animations: { self.setNeedsUpdateOfHomeIndicatorAutoHidden() }) } }
对setNeedsUpdateOfHomeIndicatorAutoHidden()的简单的赋值和调用,将轻微修改组件的Alpha属性产生淡入淡出。无论该组件是否位于一个动画块(Animation Block)中。
Container控制器
视图控制器(View Controller)的另一个新添加特性,是告知UIKit一个子视图控制器是否应该控制Home指示键可见性的机制。如果你具有足够丰富的iOS经验,你可能会驾驭容器视图控制器,更好地提升抽象和封装模式。
其中包含的这些控制器可能会发现自身已经近乎屏幕底部。如果是这样,你可能想要抛开Home指示键做事。一个简单的重写就可解决这个问题。它会返回被掩盖(Obscure)的实例,或是去掩盖一些实例。代码如下:
override func childViewControllerForHomeIndicatorAutoHidden() -> UIViewController? { return myChildController }
如果你已经指定了子控制器去标识可见性,那么重写我们前面所讨论的函数的担子现在落在了子控制器身上:
class MyChildViewController: UIViewController { override func prefersHomeIndicatorAutoHidden() -> Bool { return true } }
函数的签名允许返回值为Nil。在此情况下,UIKit将查看当前做出决策的控制器。如果我们已经选择不重写函数,那么决策就是“显示Home指示键”。
我们也可以在运行时做出决策。UIKit将再次请求调用我们刚刚讨论过的直接分配(Pass Through)函数,通知框架应再一次查询prefersHomeIndicatorAutoHidden():
override func childViewControllerForHomeIndicatorAutoHidden() -> UIViewController? { return myChildController } func initializeChildController() { myChildController = MyChildController() self.setNeedsUpdateOfHomeIndicatorAutoHidden() }
事情就是这样。
虽然我们可以将其看成一种更深思熟虑的过程,需要应用于日常iOS事件(即处理控制器)中,但是我们会发现API与现有的处理类似问题的UIKit函数几乎相同。
更新:读者问题解答
新的Home指示键是否也会位于网站底部状态条之上?
解答详细列出于webkit.org中:
设计适用于iPhone X的Web网站
开箱即用,在新iPhone X的显示屏上,Safari可严丝合缝地显示已有的网站。
我已不再是Web开发的酷爱者了,但是这看上去是如下的元标签(meta tage)解决了自动插页问题:
默认值是auto,即允许插页内容。我们也可以重置该值,设置整个屏幕的显示方式。如果喜欢使用全屏,有一个新的CSS函数constant(),允许使用预定义常量在考虑安全区域的情况下填充元素四周。这类似于iOS的safeAreaLayoutGuideAPI.
webkit.org的帖子给出了一个例子:
.post { padding: 12px; padding-left: constant(safe-area-inset-left); padding-right: constant(safe-area-inset-right); }
Bogdan的观察更理性:
我不理解为什么Apple不是默认关闭Home指示键,或是至少给用户一个选择去关闭它。尽管这是一个吸引新用户使用iPhone的特性,但是最终(可能只需使用iPhone十分钟)每个用户将记住如何切换App,这时Home指示键就成为一个烦人和干扰使用的横条。我说得不对吗?
说得好!
正如iPhone X的“刘海”(notch)已经不仅仅是看上去那一块异形状槽,它已融入到手机的硬件中,并会成为iPhone的品牌辨识,我认为在软件上具有相似特性的就是Home Indicator。它是手机的DNA组成。此外,我打赌Apple认为它的存在将会引领用户体验。它避免了“等等,为什么现在不见了?”、“它何时显示?”、“它何时隐藏?”、“在显示它时是否可以回到主界面?”之类的问题。
也就是说,我完全同意你的看法,Home指示键持续出现在屏幕上是有些多余。但是我还没有用上iPhone X,因此在实际使用之前我不做任何评论。
你是否了解,对于游戏等全屏App,Home指示键的使用情况如何?它是否会像在正常iPhone中通知和控制中心中设置的那样,被两次滑动激活?
我知道有一个API可以重写该行为,但是Apple真的、真的、真的不希望开发人员这样做。那么它适用于哪些情况呢?当然是全屏游戏。下面给出在“用户接口指南”(Human Interface Guidelines)中对此的介绍:
很少的情况下,即游戏等沉浸式App,可能新需要用户定义屏幕边缘手势。这些手势要优先于系统定义的手势。第一次滑动调用App特定的手势,第二次滑动调用系统定义手势。
任一视图控制器的重写都是很简单的:
override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge { return .top }
总结
iPhone X需做特别考虑。
这对于iOS工程师而言是否只是轻描淡写,还是需要去维护和编码的另一个视图控制器?可能是两者的混合。如果我们熟悉软件开发的连续体系的话,那么我们就明白,新的API=时间流逝+新的生态系统。当前在智能手机领域,更切实的说法是:时间流逝+Apple的生态系统=新的硬件=新的API。
我们面对的是加长的iPhone,具有不同的显示分辨,并需要处理导航栏上的相机小点,以及底框附近的两点小横条。
下回分解。