关键字:NSTextField、NSTextView、Field Editor
上一篇文章《应用内键盘事件处理》我们介绍了 Mac App 处理键盘事件的几种方式,昨天微博上有同学私信问了一个问题:
本文就介绍一下 NSTextField、NSTextView 以及键盘事件的事件处理,文末有完整代码。
Cocoa 为我们提供的文本编辑控件有 NSTextField 和 NSTextView,前者较为轻量,支持文本编辑;后者能提供更多复杂的功能,比如设置字体等。
它们在表现上明确的区别是:对于 Enter 和 Tab 键的行为不同。NSTextField 类似其他非文本编辑的 Cocoa 控件:Enter 键触发终止编辑,Tab 键令焦点移到相邻下一控件;NSTextView 则给编辑内容添加换行或者 tab 字符。
Field Editor 则是 Window 中一个特殊的 NSTextView。
通常我们把 Text Field 作为简单的文字编辑控件使用。像所有的控件一样,Text Field 有自己的 target 和 action。非法的输入将触发 Text Field 向 target 发送特殊的 error action 消息。
Text Field 由 NSTextFieldCell 和 NSTextField 组成,NSTextFieldCell 实现了大多数方法,NSTextField 继承自 NSControl,作为 NSTextFieldCell 的 Container 为其所有方法进行封装。NSTextField 提供了一些类似 textDidBeginEditing:
的 delegate 方法。
Text View 通常用于多行带有样式的复杂文字编辑。用户可以控制 Text View 的文字内容、字体、颜色、样式和其他属性。
NSTextView 是 NSText 的子类。
Field Editor 是一个 Window 中所有控件共享的一个 NSTextView。这个被共享的 Text View 会自动的插入 View Hierarchy,为正在 Editing 的 Text Field 提供文字编辑的功能,处理键盘事件和显示文字。下图为此时状态说明:
因为 Window 内的 Text Fields 处于 Editing 状态的至多只有一个,因此系统只创建了一个 NSTextView 的实例来作为 Field Editor。开发者也可以选择实现自己的 Field Editor,详细可参见: Working with the Field Editor
键盘事件处理一文中提到,键盘事件最终会进入 keyDown:
方法中,在 NSTextField 与 NSTextView 中,会由 interpretKeyEvents:
并根据键盘事件是否有绑定的 Command,向调用者发送 doCommandBySelector:
或 insertText:
消息。
因此,想要捕捉 Enter 和 Shift-Enter 事件只需在合适的地方重写相应的 Command 方法即可。
实际上我们并不需要继承它们来改写方法,NSTextFieldDelegate 和 NSTextViewDelegate 中都有对应的方法可以处理 doCommandBySelector:
的方法。
NSTextFieldDelegate 继承了 NSControlTextEditingDelegate,其中:
optional func control(_ control: NSControl, textView textView: NSTextView, doCommandBySelector commandSelector: Selector) -> Bool
可以根据 commandSelector 判断事件类型,并进行定制化的处理。方法返回 true
表示 delegate 已经处理了事件,系统将不再执行 commandSelector,返回 false
系统则会进行默认的处理。
enter
对应的 commandSelector 为 insertNewline:
。此外通过 NSApplication 中 currentEvent?.modifierFlags
的值即可判断是否同时按下了 shift
键。
在 NSTextField 中 insertNewline:
的系统行为是结束编辑,如果需要插入新的一行应该调用 textView.insertNewlineIgnoringFieldEditor:
方法,这是 NSTextField 中 Option-Enter
对应的 Command Selector 。
其实并不建议改写系统默认行为,应该考虑是否可用 Text View 代替。
相关代码片段如下:
func control(control: NSControl, textView: NSTextView, doCommandBySelector commandSelector: Selector) -> Bool { if commandSelector == #selector(insertNewline(_:)) { if let modifierFlags = NSApplication.sharedApplication().currentEvent?.modifierFlags where (modifierFlags.rawValue & NSEventModifierFlags.ShiftKeyMask.rawValue) != 0 { print("Shift-Enter detected.") } else { print("Enter detected.") } textView.insertNewlineIgnoringFieldEditor(self) return true } return false }
类似 NSTextFieldDelegate,NSTextViewDelegate 提供的相关方法为:
optional func textView(_ textView: NSTextView, doCommandBySelector commandSelector: Selector) -> Bool
方法具体实现类似 NSTextFieldDelegate 相关方法,不再赘述。
一个简单的 Demo,实现了:
完整代码: SeedLabIO/TextFieldExample · GitHub
Happy Coding :smile:.
CurrencyX 是我们开发的 Mac 上小而美的汇率 app,售价¥12,如果你觉得文章有用,可以 前往 App Store 买一个支持我们。
关注我们公众号,获取最新文章推送