iOS8自定義輸入法教程:如何創(chuàng)建第三方輸入法
iOS8帶來了很多很酷的功能,其中一個(gè)就是增加第三方輸入法作為應(yīng)用程序擴(kuò)展。我們應(yīng)當(dāng)重視這個(gè)時(shí)刻,因?yàn)閼?yīng)用程序擴(kuò)展開辟了一個(gè)全新的應(yīng)用程序種類以及付費(fèi)操作。憑借著在應(yīng)用商店中數(shù)百萬的應(yīng)用程序,開發(fā)者和用戶將迎來全新的一天。
在本帖中,我將向您展示如何為您的應(yīng)用程序創(chuàng)建一個(gè)可進(jìn)行全系統(tǒng)輸入法操作的第三方輸入法。
本教程將用Swift來完成。這是我的***個(gè)真正用Swift語言完成的項(xiàng)目,我對(duì)其十分喜愛?,F(xiàn)在,讓我們直接研究如何創(chuàng)建一個(gè)第三方輸入法。
首先,我先向大家展示一下我們要搭建的輸入法的最終效果圖。輸入法將能夠在文本框中輸入、刪除文字以及實(shí)現(xiàn)其他基本功能。諸如上下文預(yù)測、詞典等更為高級(jí)的功能超出了本教程的范圍。
創(chuàng)建Xcode項(xiàng)目
打開Xcode 6并創(chuàng)建一個(gè)新項(xiàng)目。File->New->Project
給工程命名為CustomKeyboardSample并在恰當(dāng)?shù)奈恢帽4?。這是我們的主項(xiàng)目,但是我們還需要添加擴(kuò)展。
現(xiàn)在,讓我們往項(xiàng)目中添加一個(gè)Text Field。打開Main.Storyboard文件然后在屏幕上的視圖控制器上拖放一個(gè)UITextField控件。
這是我們用來測試輸入的地方。現(xiàn)在是時(shí)候來添加擴(kuò)展了。
點(diǎn)擊File-> New -> Target,選擇在iOS/Application Extension列表中的自定義輸入法(Custom Keyboard)。給該擴(kuò)展命名為CustomKeyboard并選擇Swift編程語言。
添加擴(kuò)展
現(xiàn)在你應(yīng)該有一個(gè)名為CustomKeyboard的新目標(biāo)文件夾,其中有一個(gè)名為KeyboardViewController.swift文件。 Xcode中已經(jīng)為我們?cè)黾恿艘恍┏跏即a和這個(gè)鍵盤應(yīng)該可以運(yùn)行了(盡管沒有什么功能)。
現(xiàn)在,您可以嘗試運(yùn)行CustomKeyboardSample,并嘗試新的輸入法。
當(dāng)你點(diǎn)擊文本框的時(shí)候,系統(tǒng)鍵盤會(huì)顯示出來。我們可以使用底排的地球圖標(biāo)來切換輸入法,但是我們只有安裝我們的新鍵盤后,這個(gè)圖標(biāo)才會(huì)顯示出來。
轉(zhuǎn)到主屏幕(點(diǎn)擊菜單欄,Hardware->Home)。打開設(shè)置并轉(zhuǎn)到通用->鍵盤->鍵盤。點(diǎn)擊“添加新鍵盤”,然后選擇CustomKeyboard。 打開開關(guān)來啟用它并同意警告信息。
點(diǎn)擊完成,您可以準(zhǔn)備好開始了!
如果在模擬器上再次運(yùn)行該應(yīng)用程序,那么就可以切換輸入法。點(diǎn)擊地球圖標(biāo),直到你看到只有“Next Keyboard”按鈕的鍵盤。
現(xiàn)在是時(shí)候開始添加輸入法的按鍵了。
打開文件KeyboardViewController.h。在這個(gè)文件中,你會(huì)看到一個(gè)類KeyboardViewController繼承自UIInputViewController。這是管理視圖的鍵盤類。我們可以向包含視圖中添加按鈕,它會(huì)在鍵盤中顯示出來。
添加一個(gè)名為createButton的函數(shù)
- func createButtonWithTitle(title: String) -> UIButton {
- let button = UIButton.buttonWithType(.System) as UIButton
- button.frame = CGRectMake(0, 0, 20, 20)
- button.setTitle(title, forState: .Normal)
- button.sizeToFit()
- button.titleLabel.font = UIFont.systemFontOfSize(15)
- button.setTranslatesAutoresizingMaskIntoConstraints(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
- }
在上面的代碼中,我們以編程的方式來添加一個(gè)按鈕,并設(shè)置其屬性。我們可以用nib文件來實(shí)現(xiàn),但我們將不得不管理如此之多的按鈕。因此這是一個(gè)更好的選擇。
該代碼會(huì)調(diào)用一個(gè)名為didTapButton的響應(yīng)按鈕被按下的方法。現(xiàn)在讓我們添加一個(gè)方法。
- func didTapButton(sender: AnyObject?) {
- let button = sender as UIButton
- let title = button.titleForState(.Normal)
- var proxy = textDocumentProxy as UITextDocumentProxy
- proxy.insertText(title)
- }
在上面的方法中,我們用swift實(shí)現(xiàn)了對(duì)一個(gè)按鈕事件的處理。AnyObject類型就像是在Objective-C中的ID的對(duì)象。我們給UIButton放置了一個(gè)傳送器,然后得到該按鈕的標(biāo)題文字,這些文字是我們要在文本框中要輸入的文本文字。
要使用輸入法輸入文本,我們使用textDocumentProxy對(duì)象,并調(diào)用insertText方法。
接下來的一步是添加一個(gè)按鈕到我們的鍵盤視圖。在viewDidLoad方法下面加入兩行代碼。您可以在viewDidLoad和textDidChange方法中刪除自動(dòng)生成的代碼。
override func viewDidLoad() {
super.viewDidLoad()
let button = createButtonWithTitle("A")
self.view.addSubview(button)
}
增加了標(biāo)題為“A”到輸入法的按鈕。這是我們的關(guān)鍵所在。
現(xiàn)在,運(yùn)行應(yīng)用程序,然后點(diǎn)擊文本框,你應(yīng)該可以看到鍵盤的”A”鍵。 (你可能需要切換鍵盤)。
點(diǎn)擊這個(gè)按鈕,看看會(huì)發(fā)生什么......看!我們已經(jīng)有了文字A!
好吧,讓我們添加更多的按鍵,讓這個(gè)小子看起來像一個(gè)真正的輸入法。
讓我們修改我們?cè)趘iewDidLoad方法中加入的代碼。
現(xiàn)在有了這個(gè)新的代碼,我們創(chuàng)建了一個(gè)包含按鍵標(biāo)題的數(shù)組,同時(shí)我們也創(chuàng)造了包含這些按鍵的列表。每個(gè)按鍵都被添加到一個(gè)數(shù)組和一個(gè)UIView中,這將是我們的***排鍵列。然后向主鍵盤圖中添加該視圖。 如果你運(yùn)行這個(gè)程序,你可能只看到了P鍵,因?yàn)樗械陌粹o都在同一位置。我們需要以編程方式添加一些約束,使他們能夠在一排對(duì)齊。 因此,我們將創(chuàng)建一個(gè)新的函數(shù)來創(chuàng)建約束。
- 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)
- func addIndividualButtonConstraints(buttons: UIButton[], mainView: UIView){
- for (index, button) in enumerate(buttons) {
- 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])
- }
- }
這是一個(gè)很長的代碼,不是嗎?有了AutoLayout,你不能真正建立起它并且你需要立即添加所有約束,否則它將不工作。上面代碼的主要工作是添加邊界為1px的約束,這些約束將添加到每個(gè)按鍵的頂部和底部。它也增加了左右兩邊邊界為1px的約束,添加到相鄰鍵的左邊和右邊(或添加到行視圖,如果它是該行中的***個(gè)或***一個(gè)鍵的話)。
如果在viewDidLoad中添加調(diào)用上面函數(shù)的功能,我們應(yīng)該可以看到新的排按鍵顯示出來。
現(xiàn)在,這看起來更像是一個(gè)輸入法了。接下來的步驟是添加輸入法的其他行。要做到這一點(diǎn),讓我們做一些快速重構(gòu)。創(chuàng)建并實(shí)現(xiàn)添加每行按鍵的新方法。
- 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)
- buttons.append(button)
- keyboardRowView.addSubview(button)
- }
- addIndividualButtonConstraints(buttons, mainView: keyboardRowView)
- return keyboardRowView
- }
我們已經(jīng)基本將viewDidLoad中的代碼提取到它自己的方法中。這種新的方法將標(biāo)題文字存放在數(shù)列中,并返回包含該行所有按鈕的視圖?,F(xiàn)在,我們可以在任何一個(gè)我們想要添加的代碼中調(diào)用這段代碼。
因此,我們的新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)
- }
在上面的代碼中,我們已經(jīng)加入4行鍵,然后加入這些按鍵,后者又被添加到主視圖中的行中。
我們現(xiàn)在可以運(yùn)行代碼,但你只能看到***一排,因?yàn)樗麄兌荚谕晃恢谩?我們需要添加一些自動(dòng)布局的約束。
- func addConstraintsToInputView(inputView: UIView, rowViews: UIView[]){
- for (index, rowView) in enumerate(rowViews) {
- 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)
- }
- }
這種新方法做了類似的功能,我們?cè)黾恿?**的自動(dòng)布局代碼。它增加了相對(duì)于主視圖向行的左右兩邊以及下面的1px的約束和添加每一行和和它上面行之間的0px約束。
現(xiàn)在,我們需要從我們的viewDidLoad方法中調(diào)用這段代碼。
- 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.setTranslatesAutoresizingMaskIntoConstraints(false)
- row2.setTranslatesAutoresizingMaskIntoConstraints(false)
- row3.setTranslatesAutoresizingMaskIntoConstraints(false)
- row4.setTranslatesAutoresizingMaskIntoConstraints(false)
- addConstraintsToInputView(self.view, rowViews: [row1, row2, row3, row4])
- }
這是我們新的viewDidLoad函數(shù)。你會(huì)看到,我們把每一行的TranslatesAutoresizingMaskIntoConstraints設(shè)為了false。這是為了確保更好的使用自動(dòng)布局的方法,而不是使用約束的布局。
現(xiàn)在,如果你運(yùn)行應(yīng)用程序,你會(huì)看到輸入法均能正常自如地布局。您可以點(diǎn)擊所有的按鈕,看看它們輸入到文本框中。
還有一個(gè)小問題,非文本鍵無法正常工作(例如,退格鍵,空格鍵)。
為了解決這個(gè)問題,我們需要改變我們的didTapButton方法來添加響應(yīng)這些鍵的正確方法。
- func didTapButton(sender: AnyObject?) {
- let button = sender as UIButton
- let title = button.titleForState(.Normal) as String
- var 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語句來對(duì)按下的鍵進(jìn)行識(shí)別處理。退格鍵調(diào)用代理上的deleteBackward方法,空格鍵插入一個(gè)空格和CHG鍵改變輸入法或者系統(tǒng)輸入法或安裝下一個(gè)輸入法。
下一步是什么?
教程就到這里,本教程就如何創(chuàng)建一個(gè)基本的自定義鍵盤進(jìn)行了一個(gè)粗略的講解。你的作業(yè)是這個(gè)進(jìn)一步優(yōu)化,看看您是否可以添加更高級(jí)的功能,如使用Caps Lock鍵添加大寫字母,切換到數(shù)字/符號(hào)鍵盤方案等。
本文鏈接:http://www.cocoachina.com/ios/20140922/9706.html