我将讲解一些关于键盘扩展的基本知识,然后使用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. sublime linux下无法输入中文

    cd ~ vim sublime_imfix.c 输入 #include <gtk/gtkimcontext.h> void gtk_im_context_set_client_windo ...

  2. 以下css可以清除浮动

    .clearfix:after { content: ""; clear: both; visibility: hidden; display: block; height: 0p ...

  3. ES6中Arguments和Parameters用法解析

    原文链接 译文 ECMAScript 6 (也称 ECMAScript 2015) 是ECMAScript 标准的最新版本,显著地完善了JS中参数的处理方式.除了其它新特性外,我们还可以使用rest参 ...

  4. Python验证Url地址的正则表达式

    如下是django中做url验证的正则表达式: regex = re.compile( r'^(?:http|ftp)s?://' # http:// or https:// r'(?:(?:[A-Z ...

  5. CSS3-transform,2D动画实例

    对元素进行移动.缩放.转动.拉长 或 拉伸 全部都需要加前缀. Transform-2D转换方法:rotate()旋转.scale()缩放.skew()扭曲/倾斜.translate()位移.matr ...

  6. PHP学习路线

    0x1 0x2

  7. Caffe学习系列(14):Caffe代码阅读

    知乎上这位博主画的caffe的整体结构:https://zhuanlan.zhihu.com/p/21796890?refer=hsmyy Caffe 做train时的流程图,来自http://caf ...

  8. spring 部分配置内容备忘

    1.spring定时器简单配置: <bean name="taskJob" class="com.netcloud.mail.util.TaskJob"& ...

  9. ZUI前段框架简介

    一.说明 基于Bootstrap定制 ZUI继承了Bootstrap 3中的大部分基础内容,但出于与Bootstrap不同的目的,一些组件都进行了定制和修改.这些变化包括: 移除了部分插件的限制,增加 ...

  10. 2.AngularJS MVC

    AngularJs的MVC全部借助于$scope(作用域)实现 1.ng指令 <!doctype html> <html ng-app> <head> <me ...