参考 : http://www.cocoachina.com/ios/20140922/9706.html
打开Xcode 6并创建一个新项目。File->New->Project
给工程命名为CustomKeyboardSample并在恰当的位置保存。这是我们的主项目,但是我们还需要添加扩展。
现在,让我们往项目中添加一个Text Field。打开Main.Storyboard文件然后在屏幕上的视图控制器上拖放一个UITextField控件。
这是我们用来测试输入的地方。现在是时候来添加扩展了。
点击File-> New -> Target,选择在iOS/Application Extension列表中的自定义输入法(Custom Keyboard)。给该扩展命名为CustomKeyboard并选择Swift编程语言。
添加扩展
![](https://i-blog.csdnimg.cn/blog_migrate/df22257e07ef561cb7ed85895939b41b.png)
现在你应该有一个名为CustomKeyboard的新目标文件夹,其中有一个名为KeyboardViewController.swift文件。 Xcode中已经为我们增加了一些初始代码和这个键盘应该可以运行了(尽管没有什么功能)。
现在,您可以尝试运行CustomKeyboardSample,并尝试新的输入法。
当你点击文本框的时候,系统键盘会显示出来。我们可以使用底排的地球图标来切换输入法,但是我们只有安装我们的新键盘后,这个图标才会显示出来。
转到主屏幕(点击菜单栏,Hardware->Home)。打开设置并转到通用->键盘->键盘。点击“添加新键盘”,然后选择CustomKeyboard。 打开开关来启用它并同意警告信息。
点击完成,您可以准备好开始了!
如果在模拟器上再次运行该应用程序,那么就可以切换输入法。点击地球图标,直到你看到只有“Next Keyboard”按钮的键盘。
现在是时候开始添加输入法的按键了。
打开文件KeyboardViewController.h。在这个文件中,你会看到一个类KeyboardViewController继承自UIInputViewController。这是管理视图的键盘类。我们可以向包含视图中添加按钮,它会在键盘中显示出来。
添加一个名为createButton的函数
func createButtonWithTitle(title: String) -> UIButton {
let button = UIButton(type: .System)
button.frame = CGRectMake(0, 0, 20, 20)
button.setTitle(title, forState: .Normal)
button.sizeToFit()
button.titleLabel.font = UIFont.systemFontOfSize(15)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor(white: 1.0, alpha: 1.0)
button.setTitleColor(UIColor.darkGrayColor(), forState: .Normal)
button.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
return button
}
在上面的代码中,我们以编程的方式来添加一个按钮,并设置其属性。我们可以用nib文件来实现,但我们将不得不管理如此之多的按钮。因此这是一个更好的选择。
该代码会调用一个名为didTapButton的响应按钮被按下的方法。现在让我们添加一个方法。
func didTapButton(sender: AnyObject?) {
let button = sender as UIButton
let title = button.titleForState(.Normal)
var proxy = textDocumentProxy as UITextDocumentProxy
proxy.insertText(title)
}
在上面的方法中,我们用swift实现了对一个按钮事件的处理。AnyObject类型就像是在Objective-C中的ID的对象。我们给UIButton放置了一个传送器,然后得到该按钮的标题文字,这些文字是我们要在文本框中要输入的文本文字。
要使用输入法输入文本,我们使用textDocumentProxy对象,并调用insertText方法。
接下来的一步是添加一个按钮到我们的键盘视图。在viewDidLoad方法下面加入两行代码。您可以在viewDidLoad和textDidChange方法中删除自动生成的代码。
override func viewDidLoad() {
super.viewDidLoad()
let button = createButtonWithTitle("A")
self.view.addSubview(button)
}
增加了标题为“A”到输入法的按钮。这是我们的关键所在。
现在,运行应用程序,然后点击文本框,你应该可以看到键盘的”A”键。 (你可能需要切换键盘)。
点击这个按钮,看看会发生什么......看!我们已经有了文字A!
输入效果
![](https://i-blog.csdnimg.cn/blog_migrate/dc2034daa181b949b6dc0f8ad9fa094a.png)
好吧,让我们添加更多的按键,让这个小子看起来像一个真正的输入法。
让我们修改我们在viewDidLoad方法中加入的代码。
override func viewDidLoad() {
super.viewDidLoad()
let buttonTitles = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
var buttons = UIButton[]()
var keyboardRowView = UIView(frame: CGRectMake(0, 0, 320, 50))
for buttonTitle in buttonTitles{
let button = createButtonWithTitle(buttonTitle)
buttons.append(button)
keyboardRowView.addSubview(button)
}
self.view.addSubview(keyboardRowView)
现在有了这个新的代码,我们创建了一个包含按键标题的数组,同时我们也创造了包含这些按键的列表。每个按键都被添加到一个数组和一个UIView中,这将是我们的第一排键列。然后向主键盘图中添加该视图。
如果你运行这个程序,你可能只看到了P键,因为所有的按钮都在同一位置。我们需要以编程方式添加一些约束,使他们能够在一排对齐。
因此,我们将创建一个新的函数来创建约束。
func addIndividualButtonConstraints(buttons: [UIButton], mainView: UIView){
for (index, button) in (buttons).enumerate() {
var topConstraint = NSLayoutConstraint(item: button, attribute: .Top, relatedBy: .Equal, toItem: mainView, attribute: .Top, multiplier: 1.0, constant: 1)
var bottomConstraint = NSLayoutConstraint(item: button, attribute: .Bottom, relatedBy: .Equal, toItem: mainView, attribute: .Bottom, multiplier: 1.0, constant: -1)
var rightConstraint : NSLayoutConstraint!
if index == buttons.count - 1 {
rightConstraint = NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: mainView, attribute: .Right, multiplier: 1.0, constant: -1)
}else{
let nextButton = buttons[index+1]
rightConstraint = NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: nextButton, attribute: .Left, multiplier: 1.0, constant: -1)
}
var leftConstraint : NSLayoutConstraint!
if index == 0 {
leftConstraint = NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: mainView, attribute: .Left, multiplier: 1.0, constant: 1)
}else{
let prevtButton = buttons[index-1]
leftConstraint = NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: prevtButton, attribute: .Right, multiplier: 1.0, constant: 1)
let firstButton = buttons[0]
var widthConstraint = NSLayoutConstraint(item: firstButton, attribute: .Width, relatedBy: .Equal, toItem: button, attribute: .Width, multiplier: 1.0, constant: 0)
mainView.addConstraint(widthConstraint)
}
mainView.addConstraints([topConstraint, bottomConstraint, rightConstraint, leftConstraint])
}
}
这是一个很长的代码,不是吗?有了AutoLayout,你不能真正建立起它并且你需要立即添加所有约束,否则它将不工作。上面代码的主要工作是添加边界为1px的约束,这些约束将添加到每个按键的顶部和底部。它也增加了左右两边边界为1px的约束,添加到相邻键的左边和右边(或添加到行视图,如果它是该行中的第一个或最后一个键的话)。
如果在viewDidLoad中添加调用上面函数的功能,我们应该可以看到新的排按键显示出来。
![](https://i-blog.csdnimg.cn/blog_migrate/02d7eeb99d17dacc7799b37e924ae9f5.png)
现在,这看起来更像是一个输入法了。接下来的步骤是添加输入法的其他行。要做到这一点,让我们做一些快速重构。创建并实现添加每行按键的新方法。
func createRowOfButtons(buttonTitles: [NSString]) -> UIView {
var buttons = [UIButton]()
var keyboardRowView = UIView(frame: CGRectMake(0, 0, 320, 50))
for buttonTitle in buttonTitles{
let button = createButtonWithTitle(buttonTitle as String)
buttons.append(button)
keyboardRowView.addSubview(button)
}
addIndividualButtonConstraints(buttons, mainView: keyboardRowView)
return keyboardRowView
}
我们已经基本将viewDidLoad中的代码提取到它自己的方法中。这种新的方法将标题文字存放在数列中,并返回包含该行所有按钮的视图。现在,我们可以在任何一个我们想要添加的代码中调用这段代码。
因此,我们的新vieDidLoad方法是这样的:
override func viewDidLoad() {
super.viewDidLoad()
let buttonTitles1 = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
let buttonTitles2 = ["A", "S", "D", "F", "G", "H", "J", "K", "L"]
let buttonTitles3 = ["CP", "Z", "X", "C", "V", "B", "N", "M", "BP"]
let buttonTitles4 = ["CHG", "SPACE", "RETURN"]
var row1 = createRowOfButtons(buttonTitles1)
var row2 = createRowOfButtons(buttonTitles2)
var row3 = createRowOfButtons(buttonTitles3)
var row4 = createRowOfButtons(buttonTitles4)
self.view.addSubview(row1)
self.view.addSubview(row2)
self.view.addSubview(row3)
self.view.addSubview(row4)
}
在上面的代码中,我们已经加入4行键,然后加入这些按键,后者又被添加到主视图中的行中。
我们现在可以运行代码,但你只能看到最后一排,因为他们都在同一位置。 我们需要添加一些自动布局的约束。
func addConstraintsToInputView(inputView: UIView, rowViews: [UIView]){
for (index, rowView) in (rowViews).enumerate() {
var rightSideConstraint = NSLayoutConstraint(item: rowView, attribute: .Right, relatedBy: .Equal, toItem: inputView, attribute: .Right, multiplier: 1.0, constant: -1)
var leftConstraint = NSLayoutConstraint(item: rowView, attribute: .Left, relatedBy: .Equal, toItem: inputView, attribute: .Left, multiplier: 1.0, constant: 1)
inputView.addConstraints([leftConstraint, rightSideConstraint])
var topConstraint: NSLayoutConstraint
if index == 0 {
topConstraint = NSLayoutConstraint(item: rowView, attribute: .Top, relatedBy: .Equal, toItem: inputView, attribute: .Top, multiplier: 1.0, constant: 0)
}else{
let prevRow = rowViews[index-1]
topConstraint = NSLayoutConstraint(item: rowView, attribute: .Top, relatedBy: .Equal, toItem: prevRow, attribute: .Bottom, multiplier: 1.0, constant: 0)
let firstRow = rowViews[0]
var heightConstraint = NSLayoutConstraint(item: firstRow, attribute: .Height, relatedBy: .Equal, toItem: rowView, attribute: .Height, multiplier: 1.0, constant: 0)
inputView.addConstraint(heightConstraint)
}
inputView.addConstraint(topConstraint)
var bottomConstraint: NSLayoutConstraint
if index == rowViews.count - 1 {
bottomConstraint = NSLayoutConstraint(item: rowView, attribute: .Bottom, relatedBy: .Equal, toItem: inputView, attribute: .Bottom, multiplier: 1.0, constant: 0)
}else{
let nextRow = rowViews[index+1]
bottomConstraint = NSLayoutConstraint(item: rowView, attribute: .Bottom, relatedBy: .Equal, toItem: nextRow, attribute: .Top, multiplier: 1.0, constant: 0)
}
inputView.addConstraint(bottomConstraint)
}
}
这种新方法做了类似的功能,我们增加了最后的自动布局代码。它增加了相对于主视图向行的左右两边以及下面的1px的约束和添加每一行和和它上面行之间的0px约束。
现在,我们需要从我们的viewDidLoad方法中调用这段代码。
override func viewDidLoad() {
super.viewDidLoad()
//
let buttonTitles1 = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
let buttonTitles2 = ["A", "S", "D", "F", "G", "H", "J", "K", "L"]
let buttonTitles3 = ["CP", "Z", "X", "C", "V", "B", "N", "M", "BP"]
let buttonTitles4 = ["CHG", "SPACE", "RETURN"]
var row1 = createRowOfButtons(buttonTitles1)
var row2 = createRowOfButtons(buttonTitles2)
var row3 = createRowOfButtons(buttonTitles3)
var row4 = createRowOfButtons(buttonTitles4)
self.view.addSubview(row1)
self.view.addSubview(row2)
self.view.addSubview(row3)
self.view.addSubview(row4)
row1.translatesAutoresizingMaskIntoConstraints = false
row2.translatesAutoresizingMaskIntoConstraints = false
row3.translatesAutoresizingMaskIntoConstraints = false
row4.translatesAutoresizingMaskIntoConstraints = false
addConstraintsToInputView(self.view, rowViews: [row1, row2, row3, row4])
}
这是我们新的viewDidLoad函数。你会看到,我们把每一行的TranslatesAutoresizingMaskIntoConstraints设为了false。这是为了确保更好的使用自动布局的方法,而不是使用约束的布局。
现在,如果你运行应用程序,你会看到输入法均能正常自如地布局。您可以点击所有的按钮,看看它们输入到文本框中。
![](https://i-blog.csdnimg.cn/blog_migrate/78e7419120a3988da1edc439da4aaaf0.png)
成品图
还有一个小问题,非文本键无法正常工作(例如,退格键,空格键)。
为了解决这个问题,我们需要改变我们的didTapButton方法来添加响应这些键的正确方法。
func didTapButton(sender: AnyObject?) {
let button = sender as! UIButton
let title = button.titleForState(.Normal)! as String
let proxy = textDocumentProxy as UITextDocumentProxy
//proxy.insertText(title!)
switch title {
case "BP" :
proxy.deleteBackward()
case "RETURN" :
proxy.insertText("\n")
case "SPACE" :
proxy.insertText(" ")
case "CHG" :
self.advanceToNextInputMode()
default :
proxy.insertText(title)
}
}
这里用switch语句来对按下的键进行识别处理。退格键调用代理上的deleteBackward方法,空格键插入一个空格和CHG键改变输入法或者系统输入法或安装下一个输入法。
完整代码结果: KeyboardViewController.swift
//
// KeyboardViewController.swift
// CustomKeyboard
//
// Created by on 16/11/8.
// Copyright © 2016年 . All rights reserved.
//
import UIKit
class KeyboardViewController: UIInputViewController {
@IBOutlet var nextKeyboardButton: UIButton!
override func updateViewConstraints() {
super.updateViewConstraints()
// Add custom view sizing constraints here
}
override func viewDidLoad() {
super.viewDidLoad()
//
let buttonTitles1 = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
let buttonTitles2 = ["A", "S", "D", "F", "G", "H", "J", "K", "L"]
let buttonTitles3 = ["CP", "Z", "X", "C", "V", "B", "N", "M", "BP"]
let buttonTitles4 = ["CHG", "SPACE", "RETURN"]
var row1 = createRowOfButtons(buttonTitles1)
var row2 = createRowOfButtons(buttonTitles2)
var row3 = createRowOfButtons(buttonTitles3)
var row4 = createRowOfButtons(buttonTitles4)
self.view.addSubview(row1)
self.view.addSubview(row2)
self.view.addSubview(row3)
self.view.addSubview(row4)
row1.translatesAutoresizingMaskIntoConstraints = false
row2.translatesAutoresizingMaskIntoConstraints = false
row3.translatesAutoresizingMaskIntoConstraints = false
row4.translatesAutoresizingMaskIntoConstraints = false
addConstraintsToInputView(self.view, rowViews: [row1, row2, row3, row4])
}
//
func createButtonWithTitle(title: String) -> UIButton {
let button = UIButton(type: .System)
button.frame = CGRectMake(0, 0, 20, 20)
button.setTitle(title, forState: .Normal)
button.sizeToFit()
button.titleLabel!.font = UIFont.systemFontOfSize(15)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor(white: 1.0, alpha: 1.0)
button.setTitleColor(UIColor.darkGrayColor(), forState: .Normal)
button.addTarget(self, action: #selector(KeyboardViewController.didTapButton(_:)), forControlEvents: .TouchUpInside)
return button
}
func didTapButton(sender: AnyObject?) {
let button = sender as! UIButton
let title = button.titleForState(.Normal)! as String
let proxy = textDocumentProxy as UITextDocumentProxy
//proxy.insertText(title!)
switch title {
case "BP" :
proxy.deleteBackward()
case "RETURN" :
proxy.insertText("\n")
case "SPACE" :
proxy.insertText(" ")
case "CHG" :
self.advanceToNextInputMode()
default :
proxy.insertText(title)
}
}
func addIndividualButtonConstraints(buttons: [UIButton], mainView: UIView){
for (index, button) in (buttons).enumerate() {
var topConstraint = NSLayoutConstraint(item: button, attribute: .Top, relatedBy: .Equal, toItem: mainView, attribute: .Top, multiplier: 1.0, constant: 1)
var bottomConstraint = NSLayoutConstraint(item: button, attribute: .Bottom, relatedBy: .Equal, toItem: mainView, attribute: .Bottom, multiplier: 1.0, constant: -1)
var rightConstraint : NSLayoutConstraint!
if index == buttons.count - 1 {
rightConstraint = NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: mainView, attribute: .Right, multiplier: 1.0, constant: -1)
}else{
let nextButton = buttons[index+1]
rightConstraint = NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: nextButton, attribute: .Left, multiplier: 1.0, constant: -1)
}
var leftConstraint : NSLayoutConstraint!
if index == 0 {
leftConstraint = NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: mainView, attribute: .Left, multiplier: 1.0, constant: 1)
}else{
let prevtButton = buttons[index-1]
leftConstraint = NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: prevtButton, attribute: .Right, multiplier: 1.0, constant: 1)
let firstButton = buttons[0]
var widthConstraint = NSLayoutConstraint(item: firstButton, attribute: .Width, relatedBy: .Equal, toItem: button, attribute: .Width, multiplier: 1.0, constant: 0)
mainView.addConstraint(widthConstraint)
}
mainView.addConstraints([topConstraint, bottomConstraint, rightConstraint, leftConstraint])
}
}
func createRowOfButtons(buttonTitles: [NSString]) -> UIView {
var buttons = [UIButton]()
var keyboardRowView = UIView(frame: CGRectMake(0, 0, 320, 50))
for buttonTitle in buttonTitles{
let button = createButtonWithTitle(buttonTitle as String)
buttons.append(button)
keyboardRowView.addSubview(button)
}
addIndividualButtonConstraints(buttons, mainView: keyboardRowView)
return keyboardRowView
}
func addConstraintsToInputView(inputView: UIView, rowViews: [UIView]){
for (index, rowView) in (rowViews).enumerate() {
var rightSideConstraint = NSLayoutConstraint(item: rowView, attribute: .Right, relatedBy: .Equal, toItem: inputView, attribute: .Right, multiplier: 1.0, constant: -1)
var leftConstraint = NSLayoutConstraint(item: rowView, attribute: .Left, relatedBy: .Equal, toItem: inputView, attribute: .Left, multiplier: 1.0, constant: 1)
inputView.addConstraints([leftConstraint, rightSideConstraint])
var topConstraint: NSLayoutConstraint
if index == 0 {
topConstraint = NSLayoutConstraint(item: rowView, attribute: .Top, relatedBy: .Equal, toItem: inputView, attribute: .Top, multiplier: 1.0, constant: 0)
}else{
let prevRow = rowViews[index-1]
topConstraint = NSLayoutConstraint(item: rowView, attribute: .Top, relatedBy: .Equal, toItem: prevRow, attribute: .Bottom, multiplier: 1.0, constant: 0)
let firstRow = rowViews[0]
var heightConstraint = NSLayoutConstraint(item: firstRow, attribute: .Height, relatedBy: .Equal, toItem: rowView, attribute: .Height, multiplier: 1.0, constant: 0)
inputView.addConstraint(heightConstraint)
}
inputView.addConstraint(topConstraint)
var bottomConstraint: NSLayoutConstraint
if index == rowViews.count - 1 {
bottomConstraint = NSLayoutConstraint(item: rowView, attribute: .Bottom, relatedBy: .Equal, toItem: inputView, attribute: .Bottom, multiplier: 1.0, constant: 0)
}else{
let nextRow = rowViews[index+1]
bottomConstraint = NSLayoutConstraint(item: rowView, attribute: .Bottom, relatedBy: .Equal, toItem: nextRow, attribute: .Top, multiplier: 1.0, constant: 0)
}
inputView.addConstraint(bottomConstraint)
}
}
//--------------------------------------------------
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated
}
override func textWillChange(textInput: UITextInput?) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(textInput: UITextInput?) {
// The app has just changed the document's contents, the document context has been updated.
}
}