NSTextField сохранить фокус / первого респондента после NSPopover - PullRequest
0 голосов
/ 07 февраля 2019

Цель этого приложения - убедиться, что пользователь ввел определенный текст в NSTextField.Если этот текст отсутствует в поле, ему нельзя оставлять его.

При наличии приложения macOS с текстовым полем подкласса, кнопкой и другим универсальным NSTextField.Когда кнопка нажата, отображается NSPopover, который «прикреплен» к полю, управляемому NSViewController с именем myPopoverVC.

Например, пользователь вводит 3 в верхнем поле и затем щелкает «Показать поповер».Кнопка, которая отображает всплывающее окно и предоставляет подсказку: «Что равняется 1 + 1?»когда всплывающее окно показывает, это поле становится первым респондентом.В это время ничего не будет введено - только для этого вопроса.

Пользователь нажмет кнопку Закрыть, которая закрывает всплывающее окно.В этот момент, что должно произойти, если пользователь щелкает или вкладывает вкладку вне поля с пометкой «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, этот код выполняется, но печатает ноль.Это указывает на то, что, хотя он является первым респондентом, он не имеет связи с окном fieldEditor и не будет получать события.Почему?

Если что-то неясно, спросите.

Ответы [ 3 ]

0 голосов
/ 14 февраля 2019

Вот моя попытка с помощью Как программно начать сеанс редактирования текста в NSTextField? и Как я могу сделать так, чтобы мой NSTextField НЕ выделял свой текст при запуске приложения? :

Выбранный диапазон сохраняется в 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.

0 голосов
/ 15 февраля 2019

Огромное спасибо Виллеку за помощь и ответ, которые привели к довольно простому решению.

Большая проблема здесь заключалась в том, что, когда всплывающее окно закрывалось, поле "сосредоточено" было исходным полем.Однако кажется (по какой-то причине), что делегат редактора поля 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
}
0 голосов
/ 09 февраля 2019

Если вы создадите подкласс для каждого из ваших NSTextField, вы можете переопределить метод comeFirstResponder и заставить его отправлять self в созданный вами класс делегата, который будет хранить ссылку на текущийПервый респондент:

Суперкласс NSTextField:

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

( myRespondersDelegate : может быть вашим NSViewController)

Примечание : не используйте один и тот же суперкласс для ваших предупреждений TextFields и ViewController TextFields.Используйте этот суперкласс с дополнительной функциональностью только для TextFields, которые вы хотели бы вернуть в firstResponder после закрытия оповещения.

Делегат NSTextField:

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

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

Теперь, после того, как ваш всплывающий текст отклоненвы можете в viewWillAppear или создать функцию делегата, которая будет вызываться при всплывающем окне dismiss 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)
    }
}

Надеюсь, что это работает.

...