在iOS8 下用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作为开发语言。
添加一个text field 文本框
打开 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])
}
使用 Nib 文件
为了不用手写这些布局约束,你可以创建一个界面文件,然后直接在上面添加约束。
创建一个界面文件
右击 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
}
...
}
添加到 inputView
在 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
在你的设备上运行这个app后,如下来添加你的自定义键盘:
选择键盘。
选择添加一个新键盘。找到我们的 MorseCode
键盘:
现在重新运行我们的应用,尽情享受我们的新键盘吧。