iOS8自定义输入法教程:如何创建第三方输入法

iOS8带来了很多很酷的功能,其中一个就是增加第三方输入法作为应用程序扩展。我们应当重视这个时刻,因为应用程序扩展开辟了一个全新的应用程序种类以及付费操作。凭借着在应用商店中数百万的应用程序,开发者和用户将迎来全新的一天。

在本帖中,我将向您展示如何为您的应用程序创建一个可进行全系统输入法操作的第三方输入法。

本教程将用Swift来完成。这是我的第一个真正用Swift语言完成的项目,我对其十分喜爱。现在,让我们直接研究如何创建一个第三方输入法。

首先,我先向大家展示一下我们要搭建的输入法的最终效果图。输入法将能够在文本框中输入、删除文字以及实现其他基本功能。诸如上下文预测、词典等更为高级的功能超出了本教程的范围。

输入法效果图

创建Xcode项目

打开Xcode 6并创建一个新项目。File->New->Project

给工程命名为CustomKeyboardSample并在恰当的位置保存。这是我们的主项目,但是我们还需要添加扩展。

现在,让我们往项目中添加一个Text Field。打开Main.Storyboard文件然后在屏幕上的视图控制器上拖放一个UITextField控件。

StoryBoard效果

这是我们用来测试输入的地方。现在是时候来添加扩展了。

点击File-> New -> Target,选择在iOS/Application Extension列表中的自定义输入法(Custom Keyboard)。给该扩展命名为CustomKeyboard并选择Swift编程语言。

添加扩展

现在你应该有一个名为CustomKeyboard的新目标文件夹,其中有一个名为KeyboardViewController.swift文件。 Xcode中已经为我们增加了一些初始代码和这个键盘应该可以运行了(尽管没有什么功能)。

现在,您可以尝试运行CustomKeyboardSample,并尝试新的输入法。

当你点击文本框的时候,系统键盘会显示出来。我们可以使用底排的地球图标来切换输入法,但是我们只有安装我们的新键盘后,这个图标才会显示出来。

转到主屏幕(点击菜单栏,Hardware->Home)。打开设置并转到通用->键盘->键盘。点击“添加新键盘”,然后选择CustomKeyboard。 打开开关来启用它并同意警告信息。

点击完成,您可以准备好开始了!

如果在模拟器上再次运行该应用程序,那么就可以切换输入法。点击地球图标,直到你看到只有“Next Keyboard”按钮的键盘。

现在是时候开始添加输入法的按键了。

打开文件KeyboardViewController.h。在这个文件中,你会看到一个类KeyboardViewController继承自UIInputViewController。这是管理视图的键盘类。我们可以向包含视图中添加按钮,它会在键盘中显示出来。

添加一个名为createButton的函数

  1. func createButtonWithTitle(title: String) -> UIButton {
  2. let button = UIButton.buttonWithType(.System) as UIButton
  3. button.frame = CGRectMake(0, 0, 20, 20)
  4. button.setTitle(title, forState: .Normal)
  5. button.sizeToFit()
  6. button.titleLabel.font = UIFont.systemFontOfSize(15)
  7. button.setTranslatesAutoresizingMaskIntoConstraints(false)
  8. button.backgroundColor = UIColor(white: 1.0, alpha: 1.0)
  9. button.setTitleColor(UIColor.darkGrayColor(), forState: .Normal)
  10. button.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
  11. return button
  12. }

在上面的代码中,我们以编程的方式来添加一个按钮,并设置其属性。我们可以用nib文件来实现,但我们将不得不管理如此之多的按钮。因此这是一个更好的选择。

该代码会调用一个名为didTapButton的响应按钮被按下的方法。现在让我们添加一个方法。

  1. func didTapButton(sender: AnyObject?) {
  2. let button = sender as UIButton
  3. let title = button.titleForState(.Normal)
  4. var proxy = textDocumentProxy as UITextDocumentProxy
  5. proxy.insertText(title)
  6. }

在上面的方法中,我们用swift实现了对一个按钮事件的处理。AnyObject类型就像是在Objective-C中的ID的对象。我们给UIButton放置了一个传送器,然后得到该按钮的标题文字,这些文字是我们要在文本框中要输入的文本文字。

要使用输入法输入文本,我们使用textDocumentProxy对象,并调用insertText方法。

接下来的一步是添加一个按钮到我们的键盘视图。在viewDidLoad方法下面加入两行代码。您可以在viewDidLoad和textDidChange方法中删除自动生成的代码。

  1. override func viewDidLoad() {
  2. super.viewDidLoad()
  3. let button = createButtonWithTitle("A")
  4. self.view.addSubview(button)
  5. }

增加了标题为“A”到输入法的按钮。这是我们的关键所在。

现在,运行应用程序,然后点击文本框,你应该可以看到键盘的”A”键。 (你可能需要切换键盘)。

点击这个按钮,看看会发生什么......看!我们已经有了文字A!

输入效果

好吧,让我们添加更多的按键,让这个小子看起来像一个真正的输入法。

让我们修改我们在viewDidLoad方法中加入的代码。

  1. override func viewDidLoad() {
  2. super.viewDidLoad()
  3. let buttonTitles = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
  4. var buttons = UIButton[]()
  5. var keyboardRowView = UIView(frame: CGRectMake(0, 0, 320, 50))
  6. for buttonTitle in buttonTitles{
  7. let button = createButtonWithTitle(buttonTitle)
  8. buttons.append(button)
  9. keyboardRowView.addSubview(button)
  10. }
  11. self.view.addSubview(keyboardRowView)

现在有了这个新的代码,我们创建了一个包含按键标题的数组,同时我们也创造了包含这些按键的列表。每个按键都被添加到一个数组和一个UIView中,这将是我们的第一排键列。然后向主键盘图中添加该视图。

如果你运行这个程序,你可能只看到了P键,因为所有的按钮都在同一位置。我们需要以编程方式添加一些约束,使他们能够在一排对齐。

因此,我们将创建一个新的函数来创建约束。

  1. func addIndividualButtonConstraints(buttons: UIButton[], mainView: UIView){
  2. for (index, button) in enumerate(buttons) {
  3. var topConstraint = NSLayoutConstraint(item: button, attribute: .Top, relatedBy: .Equal, toItem: mainView, attribute: .Top, multiplier: 1.0, constant: 1)
  4. var bottomConstraint = NSLayoutConstraint(item: button, attribute: .Bottom, relatedBy: .Equal, toItem: mainView, attribute: .Bottom, multiplier: 1.0, constant: -1)
  5. var rightConstraint : NSLayoutConstraint!
  6. if index == buttons.count - 1 {
  7. rightConstraint = NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: mainView, attribute: .Right, multiplier: 1.0, constant: -1)
  8. }else{
  9. let nextButton = buttons[index+1]
  10. rightConstraint = NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: nextButton, attribute: .Left, multiplier: 1.0, constant: -1)
  11. }
  12. var leftConstraint : NSLayoutConstraint!
  13. if index == 0 {
  14. leftConstraint = NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: mainView, attribute: .Left, multiplier: 1.0, constant: 1)
  15. }else{
  16. let prevtButton = buttons[index-1]
  17. leftConstraint = NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: prevtButton, attribute: .Right, multiplier: 1.0, constant: 1)
  18. let firstButton = buttons[0]
  19. var widthConstraint = NSLayoutConstraint(item: firstButton, attribute: .Width, relatedBy: .Equal, toItem: button, attribute: .Width, multiplier: 1.0, constant: 0)
  20. mainView.addConstraint(widthConstraint)
  21. }
  22. mainView.addConstraints([topConstraint, bottomConstraint, rightConstraint, leftConstraint])
  23. }
  24. }

这是一个很长的代码,不是吗?有了AutoLayout,你不能真正建立起它并且你需要立即添加所有约束,否则它将不工作。上面代码的主要工作是添加边界为1px的约束,这些约束将添加到每个按键的顶部和底部。它也增加了左右两边边界为1px的约束,添加到相邻键的左边和右边(或添加到行视图,如果它是该行中的第一个或最后一个键的话)。

如果在viewDidLoad中添加调用上面函数的功能,我们应该可以看到新的排按键显示出来。

排按键

现在,这看起来更像是一个输入法了。接下来的步骤是添加输入法的其他行。要做到这一点,让我们做一些快速重构。创建并实现添加每行按键的新方法。

  1. func createRowOfButtons(buttonTitles: NSString[]) -> UIView {
  2. var buttons = UIButton[]()
  3. var keyboardRowView = UIView(frame: CGRectMake(0, 0, 320, 50))
  4. for buttonTitle in buttonTitles{
  5. let button = createButtonWithTitle(buttonTitle)
  6. buttons.append(button)
  7. keyboardRowView.addSubview(button)
  8. }
  9. addIndividualButtonConstraints(buttons, mainView: keyboardRowView)
  10. return keyboardRowView
  11. }

我们已经基本将viewDidLoad中的代码提取到它自己的方法中。这种新的方法将标题文字存放在数列中,并返回包含该行所有按钮的视图。现在,我们可以在任何一个我们想要添加的代码中调用这段代码。

因此,我们的新vieDidLoad方法是这样的:

  1. override func viewDidLoad() {
  2. super.viewDidLoad()
  3. let buttonTitles1 = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
  4. let buttonTitles2 = ["A", "S", "D", "F", "G", "H", "J", "K", "L"]
  5. let buttonTitles3 = ["CP", "Z", "X", "C", "V", "B", "N", "M", "BP"]
  6. let buttonTitles4 = ["CHG", "SPACE", "RETURN"]
  7. var row1 = createRowOfButtons(buttonTitles1)
  8. var row2 = createRowOfButtons(buttonTitles2)
  9. var row3 = createRowOfButtons(buttonTitles3)
  10. var row4 = createRowOfButtons(buttonTitles4)
  11. self.view.addSubview(row1)
  12. self.view.addSubview(row2)
  13. self.view.addSubview(row3)
  14. self.view.addSubview(row4)
  15. }

在上面的代码中,我们已经加入4行键,然后加入这些按键,后者又被添加到主视图中的行中。

我们现在可以运行代码,但你只能看到最后一排,因为他们都在同一位置。 我们需要添加一些自动布局的约束。

  1. func addConstraintsToInputView(inputView: UIView, rowViews: UIView[]){
  2. for (index, rowView) in enumerate(rowViews) {
  3. var rightSideConstraint = NSLayoutConstraint(item: rowView, attribute: .Right, relatedBy: .Equal, toItem: inputView, attribute: .Right, multiplier: 1.0, constant: -1)
  4. var leftConstraint = NSLayoutConstraint(item: rowView, attribute: .Left, relatedBy: .Equal, toItem: inputView, attribute: .Left, multiplier: 1.0, constant: 1)
  5. inputView.addConstraints([leftConstraint, rightSideConstraint])
  6. var topConstraint: NSLayoutConstraint
  7. if index == 0 {
  8. topConstraint = NSLayoutConstraint(item: rowView, attribute: .Top, relatedBy: .Equal, toItem: inputView, attribute: .Top, multiplier: 1.0, constant: 0)
  9. }else{
  10. let prevRow = rowViews[index-1]
  11. topConstraint = NSLayoutConstraint(item: rowView, attribute: .Top, relatedBy: .Equal, toItem: prevRow, attribute: .Bottom, multiplier: 1.0, constant: 0)
  12. let firstRow = rowViews[0]
  13. var heightConstraint = NSLayoutConstraint(item: firstRow, attribute: .Height, relatedBy: .Equal, toItem: rowView, attribute: .Height, multiplier: 1.0, constant: 0)
  14. inputView.addConstraint(heightConstraint)
  15. }
  16. inputView.addConstraint(topConstraint)
  17. var bottomConstraint: NSLayoutConstraint
  18. if index == rowViews.count - 1 {
  19. bottomConstraint = NSLayoutConstraint(item: rowView, attribute: .Bottom, relatedBy: .Equal, toItem: inputView, attribute: .Bottom, multiplier: 1.0, constant: 0)
  20. }else{
  21. let nextRow = rowViews[index+1]
  22. bottomConstraint = NSLayoutConstraint(item: rowView, attribute: .Bottom, relatedBy: .Equal, toItem: nextRow, attribute: .Top, multiplier: 1.0, constant: 0)
  23. }
  24. inputView.addConstraint(bottomConstraint)
  25. }
  26. }

这种新方法做了类似的功能,我们增加了最后的自动布局代码。它增加了相对于主视图向行的左右两边以及下面的1px的约束和添加每一行和和它上面行之间的0px约束。

现在,我们需要从我们的viewDidLoad方法中调用这段代码。

  1. override func viewDidLoad() {
  2. super.viewDidLoad()
  3. let buttonTitles1 = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
  4. let buttonTitles2 = ["A", "S", "D", "F", "G", "H", "J", "K", "L"]
  5. let buttonTitles3 = ["CP", "Z", "X", "C", "V", "B", "N", "M", "BP"]
  6. let buttonTitles4 = ["CHG", "SPACE", "RETURN"]
  7. var row1 = createRowOfButtons(buttonTitles1)
  8. var row2 = createRowOfButtons(buttonTitles2)
  9. var row3 = createRowOfButtons(buttonTitles3)
  10. var row4 = createRowOfButtons(buttonTitles4)
  11. self.view.addSubview(row1)
  12. self.view.addSubview(row2)
  13. self.view.addSubview(row3)
  14. self.view.addSubview(row4)
  15. row1.setTranslatesAutoresizingMaskIntoConstraints(false)
  16. row2.setTranslatesAutoresizingMaskIntoConstraints(false)
  17. row3.setTranslatesAutoresizingMaskIntoConstraints(false)
  18. row4.setTranslatesAutoresizingMaskIntoConstraints(false)
  19. addConstraintsToInputView(self.view, rowViews: [row1, row2, row3, row4])
  20. }

这是我们新的viewDidLoad函数。你会看到,我们把每一行的TranslatesAutoresizingMaskIntoConstraints设为了false。这是为了确保更好的使用自动布局的方法,而不是使用约束的布局。

现在,如果你运行应用程序,你会看到输入法均能正常自如地布局。您可以点击所有的按钮,看看它们输入到文本框中。

成品图

还有一个小问题,非文本键无法正常工作(例如,退格键,空格键)。

为了解决这个问题,我们需要改变我们的didTapButton方法来添加响应这些键的正确方法。

  1. func didTapButton(sender: AnyObject?) {
  2. let button = sender as UIButton
  3. let title = button.titleForState(.Normal) as String
  4. var proxy = textDocumentProxy as UITextDocumentProxy
  5. proxy.insertText(title)
  6. switch title {
  7. case "BP" :
  8. proxy.deleteBackward()
  9. case "RETURN" :
  10. proxy.insertText("\n")
  11. case "SPACE" :
  12. proxy.insertText(" ")
  13. case "CHG" :
  14. self.advanceToNextInputMode()
  15. default :
  16. proxy.insertText(title)
  17. }
  18. }

这里用switch语句来对按下的键进行识别处理。退格键调用代理上的deleteBackward方法,空格键插入一个空格和CHG键改变输入法或者系统输入法或安装下一个输入法。

下一步是什么?

教程就到这里,本教程就如何创建一个基本的自定义键盘进行了一个粗略的讲解。你的作业是这个进一步优化,看看您是否可以添加更高级的功能,如使用Caps Lock键添加大写字母,切换到数字/符号键盘方案等。

时间: 2024-12-11 14:32:27

iOS8自定义输入法教程:如何创建第三方输入法的相关文章

[Cordova/Phonegap] Cordova iOS 应用在第三方输入法的键盘弹出(点击输入框)时,页面不上移,导致输入框被键盘遮挡 的解决办法

http://blog.csdn.net/lovelyelfpop/article/details/52033045 Cordova iOS应用在使用系统自带输入法键盘的时候,聚焦文本框是会将整体webview界面上移的,如下图: 然而,如果你用的是第三方输入法(百度.搜狗.qq输入法等都是),聚焦文本框弹出键盘时,界面却不会整体上移,导致文本框被软键盘遮挡,如下图: 不仅被遮挡,靠底部的文本框还无法往上拖拽滚至可视区域. 解决办法一:第三方键盘弹出,实现界面也能上移 使用 ionic-plug

Android下创建一个输入法

输入法是一种可以让用户输入文字的控件.Android提供了一套可扩展的输入法框架,使得应用程序可以让用户选择各种类型的输入法,比如基于触屏的键盘输入或者基于语音.当安装了特定输入法之后,用户即可在系统设置中选择个输入法,并在接下来的输入场景中使用该输入法.不过在任一时刻,只能使用一个输入法. 为了在安卓系统下创建一个输入法,需要新建一个包含扩展了InputMethodService类的安卓应用,并创建一个用于设置的activity,用户可以通过它将设置选项传给输入法的service,因此,你还需

UIGestureRecognizer教程:创建自定义手势

原文链接: UIGestureRecognizer教程:创建自定义手势 如果是首次访问,你可能会想订阅我的RSS feed或者在Twitter上粉我.非常感谢你的到来! UIGestureRecognizer 来识别圆*" title=""> 学习如何使用自定义 UIGestureRecognizer 来识别圆 自定义手势可以使app感觉更独特,更有活力,从而取悦用户.如果把基本的点击.拖移和旋转手势比作iOS世界里的通用皮卡,自定义手势则是拥有个性喷漆和水动力,且闪闪

创建类似于输入法窗口的非激活窗口

原文:创建类似于输入法窗口的非激活窗口 创建类似于输入法窗口的非激活窗口 周银辉 我们注意到输入法的候选词窗口是不会被激活而获得输入焦点的, 一个很明显的现象是当你用鼠标点击该窗口时, 系统焦点不会转移到该窗口上, 原来获得焦点的窗口不会失去焦点. 这很棒, 如何实现呢? 很简单, 只要将窗口的ExStyle设置为WS_EX_NOACTIVATE(0x8000000)即可. (另外, 值得注意的是, 如果窗口在任务栏显示图标的话, 仍可以通过任务栏图标来激活它) 方式1, winform窗口中,

自定义 bundle 包的创建

在我们使用第三方框架时,常常看到XXX.bundle的文件. 我们找到该文件,显示包内容,大致看到很多资源文件:图片.配置文本.XIB文件…… 什么是Bundle文件? 简单理解,就是资源文件包.我们将许多图片.XIB.文本文件组织在一起,打包成一个Bundle文件.方便在其他项目中引用包内的资源. Bundle文件的特点? Bundle是静态的,也就是说,我们包含到包中的资源文件作为一个资源包是不参加项目编译的.也就意味着,bundle包中不能包含可执行的文件.它仅仅是作为资源,被解析成为特定

cocos2d 2.x在opengl es 2.0 下自定义着色器来创建特别酷的特效(译)

cocos2d 2.x在opengl es 2.0 下自定义着色器来创建特别酷的特效(译) (2012-12-24 13:22:17) 转载▼ 标签: it cocos2d opengl 着色器 渲染 翻译:弹涂鱼 PS:欢迎加入开发群:285275050 本文翻译自:http://www.raywenderlich.com/10862/how-to-create-cool-effects-with-custom-shaders-in-opengl-es-2-0-and-cocos2d-2-x#

Android自定义视图教程

Android自定义视图教程 Android的UI元素都是基于View(屏幕中单个元素)和ViewGroup(元素的集合),Android有许多自带的组件和布局,比如Button.TextView.RelativeLayout.在app开发过程中我们需要自定义视图组件来满足我们的需求.通过继承自View或者View的子类,覆写onDraw或者onTouchEvent等方法来覆盖视图的行为. 创建完全自定义的组件 创建自定义的组件主要围绕着以下五个方面: 绘图(Drawing): 控制视图的渲染,

树莓派3B/3B+ 清华镜像系统和安装中文输入法Fcitx及Google拼音输入法

你还在为树莓派无法安装中文输入法而到处找教程吗? 你还在为树莓派每次下载都要远隔重洋获取资源,龟速下载而烦恼吗? 为了解决这个问题,在这篇树莓派教程中,我将手把手叫你怎样安装 清华镜像系统和中文输入法Fcitx及Google拼音输入法. 步骤一:换源:将下载源从树莓派默认国外源切换到国内清华大学开源软件镜像站 在树莓派的命令行界面输入 1 sudo nano /etc/apt/sources.list 使用键盘方向键控制,在第一行开头加一个#,把下面的内容拷贝到最后一行之后,如图中的效果: 清华

spring cloud 2.x版本 Gateway自定义过滤器教程

前言 本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka-ribbon.eureka-feign和spring-gataway的实现. 参考 eureka-server eureka-client eureka-ribbon eureka-feign spring-gateway 概术 Spring Cloud Gateway内部已经提供非常多的过滤器f