swift—UITextField与 NSTextView 的键盘事

CocoaNSTextField NSTextView 的键盘事件

阅读 1889

收藏 13

2016-07-16

原文链接:blog.seedlab.io

本文就介绍一下 NSTextField、NSTextView 以及键盘事件的事件处理,文末有完整代码。

NSTextField NSTextView Field Editor

Cocoa 为我们提供的文本编辑控件有 NSTextField 和 NSTextView,前者较为轻量,支持文本编辑;后者能提供更多复杂的功能,比如设置字体等。

它们在表现上明确的区别是:对于 Enter 和 Tab 键的行为不同。NSTextField 类似其他非文本编辑的 Cocoa 控件:Enter 键触发终止编辑,Tab 键令焦点移到相邻下一控件;NSTextView 则给编辑内容添加换行或者 tab 字符。

Field Editor 则是 Window 中一个特殊的 NSTextView。

NSTextField

通常我们把 Text Field 作为简单的文字编辑控件使用。像所有的控件一样,Text Field 有自己的 target 和 action。非法的输入将触发 Text Field 向 target 发送特殊的 error action 消息。

Text Field 由 NSTextFieldCell 和 NSTextField 组成,NSTextFieldCell 实现了大多数方法,NSTextField 继承自 NSControl,作为 NSTextFieldCell 的 Container 为其所有方法进行封装。NSTextField 提供了一些类似 textDidBeginEditing: 的 delegate 方法。

NSTextView

Text View 通常用于多行带有样式的复杂文字编辑。用户可以控制 Text View 的文字内容、字体、颜色、样式和其他属性。

NSTextView 是 NSText 的子类。

Field Editor

Field Editor 是一个 Window 中所有控件共享的一个 NSTextView。这个被共享的 Text View 会自动的插入 View Hierarchy,为正在 Editing 的 Text Field 提供文字编辑的功能,处理键盘事件和显示文字。下图为此时状态说明:

pastedGraphic.png

因为 Window 内的 Text Fields 处于 Editing 状态的至多只有一个,因此系统只创建了一个 NSTextView 的实例来作为 Field Editor。开发者也可以选择实现自己的 Field Editor,详细可参见: Working with the Field Editor

处理 NSTextField NSTextView 的键盘事件

键盘事件处理一文中提到,键盘事件最终会进入 keyDown: 方法中,在 NSTextField 与 NSTextView 中,会由 interpretKeyEvents: 并根据键盘事件是否有绑定的 Command,向调用者发送 doCommandBySelector: 或 insertText: 消息。

因此,想要捕捉 Enter 和 Shift-Enter 事件只需在合适的地方重写相应的 Command 方法即可。

实际上我们并不需要继承它们来改写方法,NSTextFieldDelegate 和 NSTextViewDelegate 中都有对应的方法可以处理 doCommandBySelector: 的方法。

NSTextField

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

    }

-(BOOL)control:(NSControl*)control textView:(NSTextView*)textView doCommandBySelector:(SEL)commandSelector { 

BOOL result = NO; 

if (commandSelector == @selector(insertNewline:)) { 

// enter pressed 

        result = YES; 

    } 

else if(commandSelector == @selector(moveLeft:)) { 

// left arrow pressed 

        result = YES; 

    } 

else if(commandSelector == @selector(moveRight:)) { 

// rigth arrow pressed 

        result = YES; 

    } 

else if(commandSelector == @selector(moveUp:)) { 

// up arrow pressed 

        result = YES; 

    } 

else if(commandSelector == @selector(moveDown:)) { 

// down arrow pressed 

        result = YES; 

    } 

return result; 

NSTextView

类似 NSTextFieldDelegate,NSTextViewDelegate 提供的相关方法为:

optional func textView(_ textView: NSTextView,  

   doCommandBySelector commandSelector: Selector) -> Bool

方法具体实现类似 NSTextFieldDelegate 相关方法,不再赘述。

Demo

一个简单的 Demo,实现了:

  • 判断 NSTextField 的 Enter 和 Shift-Enter 事件,并插入新的一行;
  • 判断 NSTextView 的 Enter 和 Shift-Enter 事件。

完整代码:SeedLabIO/TextFieldExample · GitHub

Happy Coding 😄.

参考

//NSTextView的使用

fileprivate let tv:NSTextView = {

        let textView = NSTextView(frame: NSMakeRect(30, 30, 200, 30))

        return textView

    }();

override func viewDidLoad() {

        super.viewDidLoad()

 

        // Do any additional setup after loading the view.

        

        self.view.addSubview(self.tv);

        //1设置textStorage的代理,实现NSTextStorageDelegate代理协议的textStorageDidProcessEiting方法

        //实现响应用户输入

        tv.textStorage?.delegate = self;

        

        

        /***

         textStorage存储富文本

         **/

        let attributedString = NSMutableAttributedString(string: "attringbutedString" as String)

        attributedString.addAttributes([NSAttributedString.Key.foregroundColor : NSColor.green as Any], range: NSMakeRange(0, 5))

        attributedString.addAttributes([NSAttributedString.Key.font : NSFont(name: "宋体", size: 14) as Any], range: NSMakeRange(5, 10))

        tv.textStorage?.setAttributedString(attributedString)

                

    }

 

  

//实现NSTextStorageDelegate代理协议的textStorageDidProcessEiting方法

    func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {

        //文本框高度根据文字高度自适应增长

        self.perform(#selector(self.setHeightToMatchContents), with: nil, afterDelay: 0.0)

    }

    //计算当前输入文本的高度

    func naturalSize()->NSSize {

        let bounds:NSRect = self.tv.bounds;

        let layoutManager:NSLayoutManager = self.tv.textStorage!.layoutManagers[0]

        let textContainer:NSTextContainer = layoutManager.textContainers[0]

        textContainer.containerSize = NSMakeSize(bounds.size.width, 1.0e7)

        layoutManager.glyphRange(for: textContainer)

        let naturalSize: NSSize? = layoutManager.usedRect(for: textContainer).size

        return naturalSize!

    }

    //根据文本高度修改文本框对应的滚动条高度

   @objc func setHeightToMatchContents() {

        let naturalSize: NSSize = self.naturalSize()

        if let scrollView = self.tv.enclosingScrollView {

            let frame = scrollView.frame

            scrollView.frame = NSMakeRect(frame.origin.x, frame.origin.y, frame.size.width, naturalSize.height + 4)

        }

    }

}

posted @ 2019-01-28 16:41  sundaysmac  阅读(864)  评论(0编辑  收藏  举报