我将讲解一些关于键盘扩展的基本知识,然后使用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 键盘:

现在重新运行我们的应用,尽情享受我们的新键盘吧。

 

[译] 用 Swift 创建自定义的键盘的更多相关文章

  1. iOS swift 关于自定义表情键盘

    目录 输入框 键盘监听 键盘切换 表情装载 表情加载 表情输入 表情输出 表情显示 结束语 demo下载 demo图片: 输入框 为了让输入框能够随着用户输入内容变化自动变化高度,这里的输入框使用UI ...

  2. UIPickView之自定义生日键盘和城市键盘

    ////  ViewController.m//  04-键盘处理// // #import "ViewController.h"#import "XMGProvince ...

  3. 【译】Swift 字符串速查表

    [译]Swift 字符串速查表 2015-12-18 10:32 编辑: suiling 分类:Swift 来源:CocoaChina翻译活动 10 5585 Swift字符串 招聘信息: iOS高级 ...

  4. 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)

    概述 最近,有客户向我们请求开发一个前端下拉控件,需求是显示了一个列表,其中包含可由用户单独选择的项目控件,该控件将在下拉列表中显示多选TreeView(树形图). 如今WijmoJS已经实现了该控件 ...

  5. Android开发案例 - 自定义虚拟键盘

    所有包含IM功能的App(如微信, 微博, QQ, 支付宝等)都提供了Emoji表情之类的虚拟键盘,  如下图:    本文只着重介绍如何实现输入法键盘和自定义虚拟键盘的流畅切换, 而不介绍如何实现虚 ...

  6. iOS开发之自定义表情键盘(组件封装与自动布局)

    下面的东西是编写自定义的表情键盘,话不多说,开门见山吧!下面主要用到的知识有MVC, iOS开发中的自动布局,自定义组件的封装与使用,Block回调,CoreData的使用.有的小伙伴可能会问写一个自 ...

  7. ASP.NET MVC随想录——创建自定义的Middleware中间件

    经过前2篇文章的介绍,相信大家已经对OWIN和Katana有了基本的了解,那么这篇文章我将继续OWIN和Katana之旅——创建自定义的Middleware中间件. 何为Middleware中间件 M ...

  8. 带你走近AngularJS - 创建自定义指令

    带你走近AngularJS系列: 带你走近AngularJS - 基本功能介绍 带你走近AngularJS - 体验指令实例 带你走近AngularJS - 创建自定义指令 ------------- ...

  9. [转]maven创建自定义的archetype

    创建自己的archetype一般有两种方式,比较简单的就是create from project 1.首先使用eclipse创建一个新的maven project,然后把配置好的一些公用的东西放到相应 ...

随机推荐

  1. java历史

    1.产生: 1990年初sun公司James Gosling等员工开发java语言的雏形,最初被命名为Oak,定位于家用电器的控制和通讯,随后因为市场的需求,公司放弃计划,后面由于Internet的发 ...

  2. CSS浏览器兼容性与解析问题终极归纳

    1.怪异模式问题:漏写DTD声明,Firefox仍然会按照标准模式来解析网页,但在IE中会触发怪异模式.为避免怪异模式给我们带来不必要的麻烦,最好养成书写DTD声明的好习惯. 2.IE6双边距问题:在 ...

  3. hiho #1310 : 岛屿 (dfs,hash)

    题目2 : 岛屿 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 给你一张某一海域卫星照片,你需要统计: 1. 照片中海岛的数目 2. 照片中面积不同的海岛数目 3. 照 ...

  4. OpenCV人形检测Hog

    #include "iostream" #include "queue" using namespace std; #include "opencv2 ...

  5. web图片轮播实现

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  6. javascript 常用技巧

    1. 将彻底屏蔽鼠标右键 oncontextmenu=”window.event.returnValue=false” < table border oncontextmenu=return(f ...

  7. Genymotion自动化启动

      一.启动方式 命令行: player.exe --vm-name [模拟器名称]   例子: "D:\Program files\Genymobile\Genymotion\player ...

  8. redis3.0.0 集群安装详细步骤

    Redis集群部署文档(centos6系统) Redis集群部署文档(centos6系统) (要让集群正常工作至少需要3个主节点,在这里我们要创建6个redis节点,其中三个为主节点,三个为从节点,对 ...

  9. springboot 连接池wait_timeout超时设置

    使用springboot 线程池连接MySQL时,mysql数据库wait_timeout 为8个小时,所以程序第二天发现报错,在url配置了 autoReconnect=true 也不行,查询配置以 ...

  10. ERROR Cannot determine the location of the VS Common Tools Folder

    参考:ERROR Cannot determine the location of the VS Common Tools Folder   http://blog.csdn.net/m3728975 ...