macOS: возьмите Emoji из CharacterPalette (исправлено) - PullRequest
0 голосов
/ 12 июля 2020

(Это измененный вопрос, включая ответ, следующий за macOS: возьмите эмодзи из characterPalette , в котором более подробно описаны возникшие проблемы)

Справочная информация / вариант использования

У меня есть приложение, в котором вместо создания и поддержки библиотеки значков я позволяю пользователям вводить эмодзи в качестве графа-заполнителя c. Это прекрасно работает в контексте моего приложения, но меня не устраивает используемый мной механизм ввода.

Проблема

Я хотел бы упростить это, поэтому открываю the characterPalette, выберите смайлик и отобразите его либо как StringValue кнопки, либо как Label (= non-editable NSTextField).

Это не представляется возможным. В отличие от NSColorPanel или NSFontPanel, characterPanel не предоставляется инфраструктуре Какао, поэтому я не могу взять его selectedValue, установить его действие или поймать уведомление. В документации для orderFrontCharacterPalette просто сказано: Открывает палитру символов, которая ... бесполезна.

Попытки решения и обнаруженные проблемы

Я пытался работать с моим приемником как firstResponder, но в отличие от NSTextView, NSTextField не может обрабатывать эмодзи. Я нашел обходной путь, используя NSTextView с NSBox впереди, сделав его firstResponder и используя NSApp.orderFrontCharacterPalette(sender), но обнаружил, что при различных обстоятельствах, которые все, кажется, включают дополнительный вызов рисования - установка заголовка кнопки, отображение метки в SystemFont Mini размер (обычный размер работал нормально), CharacterPalette будет открываться (= в системном меню теперь предлагается «Скрыть эмодзи и символы») без отображения. (Это сохраняется до закрытия приложения, даже если вы попытаетесь открыть CharacterPalette через обычное меню или ярлык)

Для частичного решения, связанного с NSTextInputClient (неявка кажется постоянной ошибкой), см. Ответ ниже.

Ответы [ 2 ]

1 голос
/ 13 июля 2020

Средство выбора смайлов требует минимальной реализации NSTextInputClient. Например, для кнопки:

class MyButton: NSButton, NSTextInputClient {
    
    override var acceptsFirstResponder: Bool {
        get {
            return true
        }
    }
    
    override func becomeFirstResponder() -> Bool {
        return true
    }
    
    override func resignFirstResponder() -> Bool {
        return true
    }

    func insertText(_ string: Any, replacementRange: NSRange) {
        // this method is called when the user selects an emoji
        if let string = string as? String {
            self.title = string
        }
    }
    
    func setMarkedText(_ string: Any, selectedRange: NSRange, replacementRange: NSRange) {
    }
    
    func unmarkText() {
    }
    
    func selectedRange() -> NSRange {
        return NSMakeRange(0, 0)
    }
    
    func markedRange() -> NSRange {
        return NSMakeRange(NSNotFound, 0)
    }
    
    func hasMarkedText() -> Bool {
        return false
    }
    
    func attributedSubstring(forProposedRange range: NSRange, actualRange: NSRangePointer?) -> NSAttributedString? {
        return nil
    }
    
    func validAttributesForMarkedText() -> [NSAttributedString.Key] {
        return []
    }
    
    func firstRect(forCharacterRange range: NSRange, actualRange: NSRangePointer?) -> NSRect {
        // the emoji picker uses the returned rect to position itself
        var rect = self.bounds
        rect.origin.x = NSMidX(rect)
        rect.size.width = 0
        return self.window!.convertToScreen(self.convert(rect, to:nil))
    }
    
    func characterIndex(for point: NSPoint) -> Int {
        return 0
    }
    
}

NSTextInputClient требуется NSTextInputContext. NSView возвращает контекст из inputContext , если класс соответствует NSTextInputClient, если isEditable не реализован, и возвращает false. Метка не возвращает NSTextInputContext, решение - переопределить inputContext:

class MyTextField: NSTextField, NSTextInputClient {
    
    var myInputContext : NSTextInputContext?

    override var inputContext: NSTextInputContext? {
        get {
            if myInputContext == nil {
                myInputContext = NSTextInputContext(client:self)
            }
            return myInputContext
        }
    }

    // and the same methods as the button, set self.stringValue instead of self.title in insertText(_:replacementRange:)

 }
1 голос
/ 12 июля 2020

Willeke указал мне на NSTextInputClient, который до сих пор является лучшим решением. Единственный пример Apple находится в Objective C, запутан и слишком сложен для того, что я пытался сделать, поэтому я воспроизводю здесь свой код.

Предостережение: это не полная реализация NSTextInputClient, достаточно для захвата ввода смайликов

Я создал подкласс NSButton:

class TextReceiverButton: NSButton, NSTextInputClient {
    //specific methods
    func setButtonTitle(_ string: String?){
        self.title = string ?? ? 
    }

//NSTextInputClient methods    

    func insertText(_ string: Any, replacementRange: NSRange) {
       let receivedText = string as? String
       setButtonTitle(receivedText)
    }
    

    func validAttributesForMarkedText() -> [NSAttributedString.Key] {
        return [.font, .paragraphStyle, .writingDirection] 
    }

//Omitted: For anything else that wants a value, I return NSMakeRange(0, 0)/NSRect.zero or 0 as well as false for marked text and nil for attributed substring
    }

(если вы добавите протокол в свой класс, он будет предлагают заглушки для других методов)

Полный набор для NSAttributedString.Key:

[.font, .foregroundColor, .glyphInfo, .kern, .ligature, .link, .markedClauseSegment, .obliqueness, .paragraphStyle, .shadow, .spellingState,  .strikethroughColor, .strikethroughStyle, .strokeColor, .strokeWidth, .superscript, .textAlternatives, .textEffect, .toolTip, .underlineColor, .underlineStyle, .verticalGlyphForm, .writingDirection]

(я тестировал короткую форму с простыми и составными смайликами, и больше ничего не нужно.)

Действие кнопки:

@IBAction func displayEmojiInButton(_ sender: Any) {
    NSApp.orderFrontCharacterPalette(self)
view.window?.makeFirstResponder(textReceiverButton)
}

Проблемы / Ошибки

В документе NSTextInputClient говорится, что «вы можете создать подкласс NSView», а код Apple превращает NSView в полнофункциональный класс текстового представления (получение и рисование) (я не могу его создать, но полагаю, что он работал). Так что теоретически вы должны иметь возможность использовать тот же код для NSTextField, который также в конечном итоге наследуется от NSView.

Однако оказывается, что NSTextField отображает ошибку «CharacterPalette якобы открывается, но никогда не отображает», о которой я говорил ранее. ; хотя он работает с NSView. (Я не проверял это дальше). Более того, NSTextInputClient не является полной заменой NSTextView: он не получает ввод от средства просмотра с клавиатуры. (См. Ответ / комментарий Willecke для объяснения / решения этих вопросов).

Verdict

NSApp.orderFrontCharacterPalette (self) не работает 95% времени при вызове из представления в непосредственной близости от представления вкладок (в splitView рядом с TabViewController, встроенным в TabViewController), поэтому, хотя этот код может быть правильным, он также часто бесполезен, по крайней мере, до 10.13.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...