UndoManager в подклассе NSResponder (macOS)

Я пытаюсь реализовать отмену/повтор в своей модели. Поэтому я сделал свой класс модели подклассом NSResponder, а затем реализовал следующее:

примечание: этот код отредактирован на основе дополнительных исследований после комментариев.

func setAnnotations(_ newAnnotations: [Annotation]) {
    let currentAnnotations = self.annotations

    self.undoManager.registerUndo(withTarget: self, handler: { (selfTarget) in
        selfTarget.setAnnotations(currentAnnotations)
    })

    self.annotations = newAnnotations
}

Annotation - это структура.

Код внутри замыкания никогда не выполняется. Сначала я заметил, что undoManager это nil, но потом нашел этот фрагмент:

private let _undoManager = UndoManager()
override var undoManager: UndoManager {
   return _undoManager
}

Теперь undoManager больше не равно нулю, но код внутри замыкания по-прежнему не выполняется.

Что мне здесь не хватает?


person koen    schedule 10.05.2019    source источник
comment
Я не понимаю, что вы подразумеваете под закрытием, которое никогда не выполняется. Закрытие не предполагается для выполнения. Это просто закрытие, которое вы передаете менеджеру отмены для хранения. Это называется когда вы отменяете действие.   -  person matt    schedule 10.05.2019
comment
Я имел в виду, что это не выполняется, когда я отменяю - ничего не отменяется.   -  person koen    schedule 10.05.2019
comment
ничего не отменяется. Но это не то же самое, что закрытие никогда не выполняется. Единственный способ узнать, выполнено ли замыкание, поставить точку останова или записать print. Что касается того, что ничего не было отменено, я не удивлен, так как ваш код вообще не имеет смысла. У вас есть параметр newAnnotations, который никогда не используется. Строка self.annotations = annotations безумна, потому что единственная annotations в поле зрения - это self.annotations, так что вы просто устанавливаете self.annotations на себя.   -  person matt    schedule 10.05.2019
comment
Вы правы - я обновил код, надеюсь, он стал более понятным. Кстати, я обнаружил, что если я перенесу это в ViewController, это сработает, поэтому я могу просто сделать это и получить доступ к модели оттуда. Все еще хотелось бы знать, почему это не работает в подклассе NSResponder.   -  person koen    schedule 10.05.2019
comment
Я сделал это в соответствии с документами Apple: самый важный код, поддерживающий отмену, должен находиться на уровне вашей модели. Каждый объект модели в вашем приложении должен иметь возможность регистрировать вызовы отмены для всех примитивных методов, которые изменяют объект. Может быть, это уже не так (?).   -  person koen    schedule 10.05.2019
comment
Хорошо, не обращайте внимания на мою чепуху о цепочке ответчиков; Я реализовал ваш код (насколько вы его показали), и проблем нет. Значит проблема в другом.   -  person matt    schedule 10.05.2019
comment
Является ли объект, подкласс NSResponder, в цепочке респондента?   -  person Willeke    schedule 10.05.2019


Ответы (1)


Я не могу воспроизвести какую-либо проблему теперь, когда вы сделали свой код регистрации отмены понятным. Вот полный код тестового приложения (я не знаю, что такое Annotation, поэтому я просто использовал String):

import Cocoa
class MyResponder : NSResponder {
    private let _undoManager = UndoManager()
    override var undoManager: UndoManager {
        return _undoManager
    }
    typealias Annotation = String
    var annotations = ["hello"]
    func setAnnotations(_ newAnnotations: [Annotation]) {
        let currentAnnotations = self.annotations
        self.undoManager.registerUndo(withTarget: self, handler: { (selfTarget) in
            selfTarget.setAnnotations(currentAnnotations)
        })
        self.annotations = newAnnotations
    }
}

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    let myResponder = MyResponder()
    @IBOutlet weak var window: NSWindow!
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        print(self.myResponder.annotations)
        self.myResponder.setAnnotations(["howdy"])
        print(self.myResponder.annotations)
        self.myResponder.undoManager.undo()
        print(self.myResponder.annotations)
    }
}

Результат:

["hello"]
["howdy"]
["hello"]

Так что Undo работает отлично. Если у вас этого не происходит, возможно, вы каким-то образом неправильно управляете своим «модельным классом».


Кстати, правильнее написать свое закрытие регистрации так:

    self.undoManager.registerUndo(withTarget: self, handler: {
        [currentAnnotations = self.annotations] (selfTarget) in
        selfTarget.setAnnotations(currentAnnotations)
    })

Это гарантирует, что self.annotations не будет захвачен преждевременно.

person matt    schedule 10.05.2019
comment
Может быть, я неправильно управляю своей моделью, мне придется разобраться в этом. По сути, мой класс модели контролирует «состояние» моего приложения. Если пользователь добавляет «аннотацию», она будет передана из контроллера представления в модель. Я сделал его отдельным классом, чтобы он был доступен для различных контроллеров представления. Я заметил, что вы вызываете «отменить ()», так что это должно быть каким-то образом запущено. Кстати, другой контроллер представления управляет NSTextView, может быть, он подхватывает отмену до того, как она достигнет моей модели, хотя аннотация была добавлена ​​после редактирования текста? - person koen; 10.05.2019
comment
Я заметил, что вы вызываете 'undo()', так что это должно быть как-то запущено. Это то, что я сказал в своем самом первом комментарии. Отмена не происходит сама по себе; это то, что вы делаете. Вы тогда заверили меня, что делаете это. — Послушайте, я знаю только то, что вы вложили в свой вопрос. То, что написано в вашем комментарии выше, для меня не имеет смысла. Если вы хотите задать вопрос об этом, с подробностями, это было бы здорово. Я ответил на вопрос, который вы действительно задали. Вы утверждали, что ваш код отмены не вызывался (код внутри замыкания по-прежнему не выполняется). Я доказал, что это так. - person matt; 10.05.2019
comment
Спасибо, что указали, что отмена не происходит сама по себе. Я ошибочно предположил, что так и будет, если вы используете подкласс NSResponder. Приятно знать, что код отмены сам по себе работает. - person koen; 10.05.2019
comment
Я не совсем понимаю, что, по вашему мнению, вызовет отмену, если не какое-либо действие пользователя, например выбор «Отменить» в меню «Правка». Должно ли это произойти только потому, что у пользователя внезапно появилось чувство сожаления? :) Если ваш вопрос заключается в том, почему выбор «Отменить» в меню «Правка» не запускает отмену, хорошо, это еще еще одна вещь, о которой вы, возможно, захотите спросить! Но опять же, дело было не в этом вопросе. - person matt; 10.05.2019
comment
Конечно, через меню Undo/Redo ;) Но мое ложное предположение заключалось в том, что как подкласс NSResponder в конечном итоге команда отмены достигнет его. И знайте, что я знаю, что это не так, если это не в цепочке ответчика. - person koen; 10.05.2019
comment
Да, о чем я также сказал в комментарии (позже удаленном). Но это не имеет ничего общего с Undo как таковым. Это связано с тем, как работает меню! — И, пожалуйста, не говорите, конечно. Оглянитесь на свой вопрос и подумайте, как мало важной информации вы на самом деле дали. Вы никогда не говорили ничего ни о каком меню! Ваши вопросы, да и ваши комментарии, если уж на то пошло, очень плохо сформулированы. У нас есть только слова, поэтому вы должны быть ясны и полны в отношении того, что вы делаете и что идет не так. - person matt; 10.05.2019