NSPopover之后,NSTextField保持焦点/第一响应者

时间:2019-02-07 01:42:16

标签: swift cocoa nstextfield nspopover

此应用程序的目的是确保用户已在NSTextField中输入了某些文本。如果该文本不在字段中,则不应允许他们离开该字段。

给出一个带有子类文本字段,一个按钮和另一个通用NSTextField的macOS应用。单击该按钮时,将显示一个NSPopover,它“附加”到一个由名为myPopoverVC的NSViewController控制的字段。

例如,用户在顶部字段中输入3,然后单击“显示弹出窗口”按钮,该按钮显示弹出窗口并提示:“ 1 + 1等于多少?”

enter image description here

请注意,此弹出窗口有一个标为1st resp的字段,因此当弹出窗口显示时,该字段将成为第一响应者。目前不会输入任何内容-仅用于此问题。

用户将单击“关闭”按钮,以关闭弹出窗口。届时,如果用户单击或跳开字段中带有“ 3”的选项卡,该应用程序将不允许该移动-可能会发出哔声或其他消息。但是,当弹出窗口关闭并且用户按下Tab键时,会发生什么情况

enter image description here

即使其中带有'3'的字段具有焦点环,它也应再次指示该窗口中的第一响应者,用户仍可以单击或远离它作为textShouldEndEditing函数不被调用。在这种情况下,我单击了弹出窗口中的关闭按钮,“ 3”字段具有聚焦环,然后单击选项卡,然后转到下一个字段。

这是子类别的文本字段中的函数,在将文本输入到该字段后,该函数可以正常工作。在这种情况下,如果用户键入3并按Tab键,则光标将停留在该字段中。

override func textShouldEndEditing(_ textObject: NSText) -> Bool {

    if self.aboutToShowPopover == true {
       return true
    }

    if let editor = self.currentEditor() { //or use the textObject
        let s = editor.string

        if s == "2" {
            return true
        }

        return false
    }

showPopover按钮代码将aboutToShowPopover标志设置为true,这将允许子类显示弹出窗口。 (当弹出窗口关闭时设置为false)

所以问题是当弹出框关闭时,如何将firstResponder状态返回到原始文本字段?它似乎具有第一响应者状态,并且认为它具有该状态,尽管未调用textShouldEndEditing。 如果在该字段中键入另一个字符,则一切正常。就像窗口的字段编辑器和其中带有“ 3”的字段断开连接一样,因此字段编辑器不会将调用传递给该字段。

按钮将调用一个包含以下内容的函数:

    let contentSize = myPopoverVC.view.frame
    theTextField.aboutToShowPopover = true
    parentVC.present(myPopoverVC, asPopoverRelativeTo: contentSize, of: theTextField, preferredEdge: NSRectEdge.maxY, behavior: NSPopover.Behavior.applicationDefined)
    NSApplication.shared.activate(ignoringOtherApps: true)

NSPopover关闭为

parentVC.dismiss(myPopoverVC)

另一条信息。我将这段代码添加到了子类化的NSTextField控件中。

override func becomeFirstResponder() -> Bool {
    let e = self.currentEditor()
    print(e)
    return super.becomeFirstResponder()
}

当弹出窗口关闭并且textField成为Windows的第一响应者时,该代码将执行,但输出nil。这表明虽然它是第一个响应者,但它与窗口fieldEditor无关,并且不会接收事件。为什么?

如果不清楚,请询问。

3 个答案:

答案 0 :(得分:2)

如果对每个NSTextField进行子类化,则可以覆盖方法 becomeFirstResponder ,并将其发送 self 到要创建的委托类,该委托类将保留对当前的第一响应者:

NSTextField超类:

override func becomeFirstResponder() -> Bool {
        self.myRespondersDelegate.setCurrentResponder(self)
        return super.becomeFirstResponder()
    }

myRespondersDelegate :可以选择是您的NSViewController)

注意 :不要将相同的超类用于警报TextField和ViewController TextField。将此父类与添加的功能一起使用,仅适用于您希望在警报关闭后返回至firstResponder的TextField。

NSTextField委托:

class MyViewController: NSViewController, MyFirstResponderDelegate {
    var currentFirstResponderTextField: NSTextField?

    func setCurrentResponder(textField: NSTextField) {
        self.currentFirstResponderTextField = textField
    }
}

现在,关闭弹出窗口后,您可以在 viewWillAppear 中创建一个委托函数,该函数将在弹出窗口中弹出 didDismisss (取决于弹出窗口的方式)已实施,我将显示委托选项) 检查是否存在TextField,然后重新创建 firstResponder

弹出代理:

class MyViewController: NSViewController, MyFirstResponderDelegate, MyPopUpDismissDelegate {
    var currentFirstResponderTextField: NSTextField?

    func setCurrentResponder(textField: NSTextField) {
        self.currentFirstResponderTextField = textField
    }

    func didDismisssPopUp() {
        guard let isLastTextField = self.currentFirstResponderTextField else  {
            return
        }
        self.isLastTextField?.window?.makeFirstResponder(self.isLastTextField)
    }
}

希望它能起作用。

答案 1 :(得分:2)

这是我在How can one programatically begin a text editing session in a NSTextField?How can I make my NSTextField NOT highlight its text when the application starts?的帮助下进行的尝试:

所选范围保存在textShouldEndEditing中,并在becomeFirstResponder中恢复。 insertText(_:replacementRange:)开始进行编辑会话。

var savedSelectedRanges: [NSValue]?

override func becomeFirstResponder() -> Bool {
    if super.becomeFirstResponder() {
        if self.aboutToShowPopover {
            if let ranges = self.savedSelectedRanges {
                if let fieldEditor = self.currentEditor() as? NSTextView {
                    fieldEditor.insertText("", replacementRange: NSRange(location: 0, length:0))
                    fieldEditor.selectedRanges = ranges
                }
            }
        }
        return true
    }
    return false
}

override func textShouldEndEditing(_ textObject: NSText) -> Bool {
    if super.textShouldEndEditing(textObject) {
        if self.aboutToShowPopover {
            let fieldEditor = textObject as! NSTextView
            self.savedSelectedRanges = fieldEditor.selectedRanges
            return true
        }
        let s = textObject.string
        if s == "2" {
            return true
        }
    }
    return false
}

也许重命名aboutToShowPopover

答案 2 :(得分:0)

非常感谢Willeke的帮助和答案,这导致了一个非常简单的解决方案。

这里的大问题是,当弹出窗口关闭时,“焦点”字段是原始字段。但是,由于某种原因,似乎Windows字段编辑器委托与该字段断开了连接,因此诸如control:textShouldEndEditing之类的功能并未传递给问题中的子类字段。

当该字段成为第一响应者时执行此行似乎将Windows字段编辑器与此字段重新连接,以便它将接收委托消息

fieldEditor.insertText("", replacementRange: range)

所以最终的解决方案是以下两个功能的组合。

override func textShouldEndEditing(_ textObject: NSText) -> Bool {

    if self.aboutToShowPopover == true {
        return true
    }

    let s = textObject.string

    if s == "2" {
        return true
    }

    return false
}

override func becomeFirstResponder() -> Bool {

    if super.becomeFirstResponder() == true {
        if let myEditor = self.currentEditor() as? NSTextView {
            let range = NSMakeRange(0, 0)
            myEditor.insertText("", replacementRange: range)
        }
        return true
    }

    return false
}