本文翻译自 How to make a custom keyboard in iOS 8 using Swift
我将讲解一些关于键盘扩展的基本知识,然后使用iOS 8 提供的新应用扩展API来创建一个莫斯码键盘。大概需要你花20多分钟来走完所有的步骤。 完整代码
一个自定义的键盘会替换系统的键盘,来提供给用户一个新的文本输入方法,或者输入哪些iOS系统还不支持的语言。一个自定义键盘的基本功能很简单:响应点击,手势或者其它输入事件以及在当前的文本输入对象的文本插入点上提供非属性化的NSString对象的文本。
当用户选择了一个键盘,那么当用户打开一个app时,这个键盘会作为默认的键盘显示。因此这个键盘必须允许用户切换到另一个键盘。
对于每个自定义键盘,有两个开发要素:
信任 : 你的自定义键盘可以访问用户输入的每个字符,所以你和你用户之间的信任非常重要。
下一个键盘按键 : 能够让用户切换另一个键盘这种可见性的功能应该是一个键盘用户界面的一部分;你必须提供这个切换功能。
注意:如果你只需要添加几个按钮到系统的键盘,你应该查看 Custom Views for Data Input
有一些特定的输入对象是你的自定义键盘没资格输入的:安全领域(例如密码输入框), 电话键盘对象(如在通讯录中的电话号码输入框)。
你的自定义键盘不能访问输入视图的层级结构,不能控制光标和选择文本。
另外,自定义键盘无法在顶行以上显示任何东西(如系统键盘,当你在顶行长按一个按键时)。
默认情况下,一个键盘是没有网络访问权限的,而且也无法与键盘的容器app分享文件。为了获得这些权限,可以在Info.plist 文件中设置 RequestsOpenAccess
这个布尔类型的键的值为 YES。 做这些会扩展键盘的沙盒,如 Establishing and Maintaining User Trust. 中描述的。
如何你这么做来申请开放权限,你的键盘会获得一下功能,每一个都伴随着责任:
访问位置服务和 Address BOOK 数据库,在第一次访问时会要求申请用户权限。
可以与包含键盘的app共享一个容器,例如这样可以允许在包含键盘的app里面提供一个自定义的词库管理界面。
能够发送键盘的点击和其它输入事件到服务端去处理。
访问iCloud,例如确保同一个用户的键盘的设置和你的自动更正词库在所有设备上同步。
通过包含键盘的app访问Game Center 和 应用内购买。
如果你设计你的键盘支持手机设备管理(MDM),那么还可以允许与管理的app一起工作。
确保你阅读了 Designing for User Trust ,它描述了在你申请开放权限的情况下,你尊重和保护用户数据的责任。
下面的图片显示了在一个运行的键盘中一些重要的对象,并且显示了在一个典型的开发流程中这些对象来源于哪里。在一个最基本的形式中,我们有一个app包含了键盘扩展和一个控制这个键盘和响应用户事件的 UIInputViewController
对象。
这个自定义的键盘模版包含一个 UIInputViewController
的子类,这是你的键盘的主视图控制器。让我们看看它的接口是怎么定义的:
class UIInputViewController : UIViewController, UITextInputDelegate, NSObjectProtocol { var inputView: UIInputView! var textDocumentProxy: NSObject! { get } func dismissKeyboard() func advanceToNextInputMode() // This will not provide a complete repository of a language's vocabulary. // It is solely intended to supplement existing lexicons. func requestSupplementaryLexiconWithCompletion(completionHandler: ((UILexicon!) -> Void)!) }
inputView
是这个键盘的视图,与 view
属性一样
dismissKeyboard
方法可以被调用来关闭键盘视图
advanceToNextInputMode
是用来切换键盘的
textDocumentProxy
是你将用来与当前的文本输入进行交互的对象。
例如:
self.textDocumentProxy.insertText("We ❤ Swift") // inserts the string "We ❤ Swift" at the insertion point self.textDocumentProxy.deleteBackward() // Deletes the character to the left of the insertion point
UIInputViewController
实现了 UITextInputDelegate
协议,当文本或者选择的文本发生变化时,会使用 selectionWillChange
, selectionDidChange
, textWillChange
和 textDidChange
消息来通知你。 我们将创建一个简单的键盘,可以输入点和破折号,切换键盘,删除一个字符以及关闭键盘。这个例子只通过代码来创建用户界面。我们也可以使用Nib 文件来创建界面-这个会在教程末尾涉及到。 加载Nibs 文件可能会对性能产生负面影响。
打开Xcode 6, 创建一个新的“Single Page Application” 项目,选择 Swift作为开发语言。
打开 Main.storyboard
,然后从 Component Library 中拖动一个文本框。我们将在后面使用这个来测试我们的键盘。
把这个文本框居中,添加必要的约束。
暗示: 如果你在 viewDidLoad
中调用 textField.becomeFirstResponder()
那么当你打开这个app时键盘就会打开。
在navigator中选择项目文件,点击 + 号添加一个新target。
选择 Application Extension
,使用 Custom Keyboard
模版, 命名为 MorseCodeKeyboard
。
这样就会创建一个新的组,名叫 MorseCodeKeyboard
,里面包含了两个文件 KeyboardViewController.swift
和 Info.plist
。
打开 KeyboardViewController.swift
文件。这个模版键盘有一个已经创建好的按钮,用来进行切换键盘的。把这些代码从 viewDidLoad
中移到一个新的方法 addNextKeyboardButton
中。
func addNextKeyboardButton() { self.nextKeyboardButton = UIButton.buttonWithType(.System) as UIButton ... var nextKeyboardButtonBottomConstraint = NSLayoutConstraint(item: self.nextKeyboardButton, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 1.0, constant: -10.0) self.view.addConstraints([nextKeyboardButtonLeftSideConstraint, nextKeyboardButtonBottomConstraint]) }
创建一个 addKeyboardButtons
方法,然后在 viewDidLoad
中调用它。这样会有助于组织代码。现在我们只是有了几个按钮,但是在实际的项目中会有更多的按钮。 在 addKeyboardButtons
中调用 addNextKeyboardButton
。
class KeyboardViewController: UIInputViewController { ... override func viewDidLoad() { super.viewDidLoad() addKeyboardButtons() } func addKeyboardButtons() { addNextKeyboardButton() } ... }
现在添加点按钮。 创建一个类型为 UIButton!
为的 dotButton
属性。
class KeyboardViewController: UIInputViewController { var nextKeyboardButton: UIButton! var dotButton: UIButton! ... }
添加一个 addDot
方法。 以一个系统类型的按钮来初始化这个 dotButton
属性。给 TouchUpInside
事件添加一个回调。 设置一个更大的字体和添加一个圆角。 添加约束来把这个按钮放在离水平中心位置左边 50个点,垂直居中的位置。 代码与 nextKeyboardButton
的类似。
func addDot() { // initialize the button dotButton = UIButton.buttonWithType(.System) as UIButton dotButton.setTitle(".", forState: .Normal) dotButton.sizeToFit() dotButton.setTranslatesAutoresizingMaskIntoConstraints(false) // adding a callback dotButton.addTarget(self, action: "didTapDot", forControlEvents: .TouchUpInside) // make the font bigger dotButton.titleLabel.font = UIFont.systemFontOfSize(32) // add rounded corners dotButton.backgroundColor = UIColor(white: 0.9, alpha: 1) dotButton.layer.cornerRadius = 5 view.addSubview(dotButton) // makes the vertical centers equa; var dotCenterYConstraint = NSLayoutConstraint(item: dotButton, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1.0, constant: 0) // set the button 50 points to the left (-) of the horizontal center var dotCenterXConstraint = NSLayoutConstraint(item: dotButton, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1.0, constant: -50) view.addConstraints([dotCenterXConstraint, dotCenterYConstraint]) }
使用 textDocumentProxy
实现 dotButton
的回调。
func didTapDot() { var proxy = textDocumentProxy as UITextDocumentProxy proxy.insertText(".") }
在 addKeyboardButtons
中调用 addDot
。
func addKeyboardButtons() { addDot() addNextKeyboardButton() }
对于 dash
, delete
, hideKeyboard
按钮,过程类似。
代码类似于 dotButton
,为了把它对称地放在水平中心位置,只需要改变水平约束的常量即可:
func addDash() { ... // set the button 50 points to the left (-) of the horizontal center var dotCenterXConstraint = NSLayoutConstraint(item: dotButton, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1.0, constant: -50) view.addConstraints([dashCenterXConstraint, dashCenterYConstraint]) } func didTapDash() { var proxy = textDocumentProxy as UITextDocumentProxy proxy.insertText("_") }
删除按钮会使用 deleteBackward
方法从 textDocumentProxy
中删除一个字符。 这个布局约束与 nextKeyboardButton
对称( .Left -> .Right, .Bottom-> .Top)。
func addDelete() { deleteButton = UIButton.buttonWithType(.System) as UIButton deleteButton.setTitle(" Delete ", forState: .Normal) deleteButton.sizeToFit() deleteButton.setTranslatesAutoresizingMaskIntoConstraints(false) deleteButton.addTarget(self, action: "didTapDelete", forControlEvents: .TouchUpInside) deleteButton.backgroundColor = UIColor(white: 0.9, alpha: 1) deleteButton.layer.cornerRadius = 5 view.addSubview(deleteButton) var rightSideConstraint = NSLayoutConstraint(item: deleteButton, attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1.0, constant: -10.0) var topConstraint = NSLayoutConstraint(item: deleteButton, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1.0, constant: +10.0) view.addConstraints([rightSideConstraint, topConstraint]) } func didTapDelete() { var proxy = textDocumentProxy as UITextDocumentProxy proxy.deleteBackward() }
hideKeyboardButton
会在点击时,调用 dismissKeyboard
来隐藏键盘:
func addHideKeyboardButton() { hideKeyboardButton = UIButton.buttonWithType(.System) as UIButton hideKeyboardButton.setTitle("Hide Keyboard", forState: .Normal) hideKeyboardButton.sizeToFit() hideKeyboardButton.setTranslatesAutoresizingMaskIntoConstraints(false) hideKeyboardButton.addTarget(self, action: "dismissKeyboard", forControlEvents: .TouchUpInside) view.addSubview(hideKeyboardButton) var rightSideConstraint = NSLayoutConstraint(item: hideKeyboardButton, attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1.0, constant: -10.0) var bottomConstraint = NSLayoutConstraint(item: hideKeyboardButton, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1.0, constant: -10.0) view.addConstraints([rightSideConstraint, bottomConstraint]) }
为了不用手写这些布局约束,你可以创建一个界面文件,然后直接在上面添加约束。
右击 MorseCodeKeyboard
组,然后选择 New File.
选择 User Interface 和 View 模版。 命名为 CustomKeyboardInterface
选择 File’s Owner ,改变类名为 KeyboardViewController
在视图中添加一个按钮,设置标题为 We ❤ Swift
。 界面类似下面这样:
在 init(nibName, bundle)
构造器中加载 CustomKeyboard
nib 文件
class KeyboardViewController: UIInputViewController { ... var customInterface: UIView! init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) var nib = UINib(nibName: "CustomKeyBoardInterface", bundle: nil) let objects = nib.instantiateWithOwner(self, options: nil) customInterface = objects[0] as UIView } ... }
在 viewDidLoad
方法中,添加自定义的界面到inputView中。
class KeyboardViewController: UIInputViewController { ... override func viewDidLoad() { super.viewDidLoad() view.addSubview(customInterface) ... } ... }
class KeyboardViewController: UIInputViewController { ... @IBAction func didTapWeheartSwift() { var proxy = textDocumentProxy as UITextDocumentProxy proxy.insertText("We ❤ Swift") } ... }
右键这个按钮,然后点击 touchUpInside
并拖动到 didTapWeHeartSwift
这个 IBAction中
最后,代码应该是 这样的 。
在你的设备上运行这个app后,如下来添加你的自定义键盘:
选择键盘。
选择添加一个新键盘。找到我们的 MorseCode
键盘:
现在重新运行我们的应用,尽情享受我们的新键盘吧。