WWDC 2017 - iOS 11 里 App 终于可以密码自动填充了

视频: WWDC 2017 - Session 206 - Introducing Password AutoFill for Apps
原文: iOS 11 里 App 终于可以密码自动填充了
作者: kemchenj

密码自动填充

iOS 11 新增了密码自动填充的 API,可以让用户在 App 里使用保存在 Safari 以及 keychain 里的密码。

iOS 以及 macOS 上的 Safari 都内建了密码管理器,每次我们在网页里登录时,Safari 都会询问我们是否要保存密码,然后当我们下一次登录同一个网站的时候,Safari 就会为我们自动填充之前保存的密码。并且这些密码会通过 iCloud keychain 同步到用户所有设备上。

iOS 11 更进了一步,让这些已经保存在 keychain 的密码显示在 App 的虚拟键盘上,接下来让我们来看个 demo:

Screen Shot 2017-07-07 at 5.42.08 P

这里展示的是一个很常见的登录页面,当我们点击输入框的时候,键盘上的 QuickType bar 就会显示出我们之前保存过密码。

Screen Shot 2017-07-07 at 5.42.14 P

点击一下之后,就会把账户和密码自动填充上去。

让 QuickType bar 正确地出现

让 QuickType bar 出现有这么几个条件:

  • 密码已经保存在 keychain 里
  • 使用原生的 UITextFieldUITextView,或者是遵循 <UITextInput> 的控件
  • iOS 会智能判断哪一个控件要展示 QuickType bar
  • 也可以由开发者手动控制让 QuickType bar 出现

我们可以通过修改 textContentType 来实现 QuickType bar 的呈现,这是 iOS 10 新引入的一个属性,属于 UITextInputTraits 协议的一部分,UITextFieldUITextView 都遵守这个协议,通过修改这个属性我们可以让 iOS 提前知道用户会输入什么类型的内容,让文本补齐文本纠正更具有针对性。

// UITextInputTraits Protocol
// 让 iOS 提前知道用户会输入什么类型的内容
optional var textContentType: UITextContentType! { get set }

UITextContentType 里有很多预设值可以满足我们的需求,例如昵称,地点,组织名,街道名字等等。在 iOS 11 里又引入了两个新的预设值。

struct UITextContentType {
...
static let username: UITextContentType
static let password: UITextContentType
}

适配密码自动填充功能很简单,只要把 UITextField 或者 UITextView 的 textContentType 为 .username.emailAddress 或者 .password 就可以了。除了通过代码修改,还可以通过 Interface Builder 进行设置。

如果 UITextFieldtextContentType 设置为 .password,即使没有把 isSecureTextEntry 设置 true,也会显示 QuickType bar。

Screen Shot 2017-07-08 at 2.56.58 P

在我们设置好 textContentType 之后,QuickType bar 确实显示出来了,但还没有显示相应的账户密码出来让用户选择,等一下我们再介绍如何显示登录信息出来,但看到这个标志,就意味着 iOS 已经准备好自动填充功能了。点击右边的钥匙 icon,就会弹出 Touch ID 验证。

Screen Shot 2017-07-08 at 3.33.05 P

验证完成后就会显示之前保存的密码,点选了其中一个账户密码之后就会自动填充上去了。

保证把登录信息正确的输入

适配须知

  • iOS 会自动填充 username 控件,以及 password 控件。
  • 即使你的 App 阻止 firstResponder 的修改,但打开密码列表的操作的优先级还是更高。
  • Touch ID 会让 App 进入 inactive 的状态。所以不要在 App 进入后台的时候修改登录界面的 UI。

最佳实践

  • 不要在 App 进入后台的时候关闭掉登录界面。
  • 通过 textField 的 text 属性读取信息。
  • 使用 “didChange” 的代理方法或者通知去验证信息。可以在这些通知发出,或者是代理方法被调用时去读取登录信息。
UITextFieldTextDidChange: Notification.Name
UITextViewTextDidChange: Notification.Name
protocol UITextViewDelegate { optional public func textViewDidChange(...) }
protocol UITextInputDelegate { public func textDidChange(...) }

让 QuickType bar 展示正确的登录信息

刚刚我们展示了如何适配自动填充功能,但只有一个🔑出现,并没有把相应的登录信息显示出来。如果你已经适配了 Universal Link,那网站跟 App 就已经关联起来了,iOS 就会把正确的登录信息显示在 QuickType bar 上。

但如果你还没有适配 Universal Link,没关系,接下来我们来介绍另外一种方式。

Screen Shot 2017-07-08 at 5.48.15 P

左边代表的是你的 App,右边是你的服务器,App 内部声明与之相关联的网站,iOS 就会向服务器发起一个请求(/.well-known/apple-app-site-association),如果服务器返回了正确格式的 JSON 文件,里面会声明一系列相关联的 App,两者相对应的话,那系统就会将 App 和网站关联起来。

Screen Shot 2017-07-08 at 5.55.48 P

App 需要做的很简单,在项目设置里,把 Associated Domains 的功能打开,然后添加一个 domain,内容填 webcredentials: 加上你的服务器域名(在这里是webcredentials:shiny.example.com)。

Screen Shot 2017-07-08 at 6.14.58 P

如果之前没有设置过 Associated Domains,那记得去苹果的开发者网站里,把 App 的 Associated Domains 功能给打开。

Screen Shot 2017-07-08 at 6.04.53 P

接下来我们还需要让服务端认可这个关联操作,建立一个名为 apple-app-site-association 的 JSON 文件,格式如上图,apps 字段里填上网站相关联的 App 的数组,每个 App 都填上团队 ID 加上 bundle ID 就可以了(基本上跟 Universal Link 一样)。最后把这个 JSON 文件放在服务器根目录下的 .well-known 文件夹里就可以了。

测试的时候记得只有在真机上才能正确地测试,完成了上面的设置之后,我们的 App 就可以呈现出正确的登录信息了。

Screen Shot 2017-07-08 at 6.16.54 P

测试的时候,我们可以连接上手机,打开 Console,通过 filter 找到 swcd 命令,每次编译运行的时候它都会告诉我们有没有与服务器成功地建立起链接。

第三方登录服务

前面我们介绍了如何关联自己的服务器和域名,但还有一种情况,登录第三方服务,例如微博,Twitter 这些社交账户,这里我们可以使用 SafariViewController 去完成登录,一方面是因为 Safari 本身就有带有登录信息填充功能,另一方面是 Safari 对于 App 来说是一个黑盒,可以让用户信息得到很好的保护。

Screen Shot 2017-07-08 at 6.31.50 P

现在 SafariViewController 添加了很多自定义项,可以让它看起来与 App 更加一致。在这里左上角的按钮被改为了 Cancel,原有的 Done 按钮在这个情境下会显得很奇怪,整体 UI 也调整成了蓝底白字。

Safari 更多的自定义项,请查看 Session 225 - What’s New in Safari View Controller。

关于使用 SaraiViewController 完成 OAuth 的流程,请查看 Session 504 - Introducing Safari View Controller

总结

  • 登录流程阻碍过多是让用户放弃 App 的一个主要因素。通过登录信息自动填充可以让登录步骤减少,借此优化用户体验。
  • 系统会智能地判断哪些地方需要密码自动填充。但我们也需要给系统足够的信息去进行判断。
  • 如何保证密码自动填充正常工作:
    • 使用 UITextContentType.usernameUITextContentType.password
    • 正确设置 Associated Domains 服务的 webcrendtials 项