前提・実現したいこと
undoManagerを使っているtextViewの文章の戻る・進む操作をmodel化してviewControllerと切り離したいが、viewControllerに記述していた時のコードをそのままmodelに移動するとundo・redo操作に登録した値が更新されてしまい、結果的に戻る・進むを行うと意図しない値がtextViewに反映されてしまう。
viewControllerに記述していた時のコード
載せているのはundoManagerに関わる最低限の記述のみですが、うまく動いていました。
import UIKit class ViewController: UIViewController { @IBOutlet weak var textView: UITextView! { didSet { textView.delegate = self } } // テキストビューの1操作前のテキストを保存するストアド private var oldText:String = "" private let textViewUndoManager = UndoManager() // 画面の戻るボタンをタップ @IBAction func undo(_ sender: Any) { if(textViewUndoManager.canUndo){ textViewUndoManager.undo() oldText = textView.text } } // 画面の進むボタンをタップ @IBAction func redo() { if(textViewUndoManager.canRedo){ textViewUndoManager.redo() oldText = textView.text } } // undo/redo操作の登録処理 func registerUndo(text: String) { if (textViewUndoManager.isUndoRegistrationEnabled) { textViewUndoManager.registerUndo(withTarget: self, handler: { _ in // この行実行時点での現在のテキストビューの値に反映させる操作を登録して進む操作を実装 if let currentText = self.textView.text { self.registerUndo(text: currentText) } // textViewDidChangeから受け取った一つ前の値を登録して戻る操作を実装 self.textView.text = text }) } } } extension ViewController: UITextViewDelegate{ // textViewの変更を検知 func textViewDidChange(_ textView: UITextView) { if (textView.markedTextRange == nil) { // 現在のテキストより1操作分前のテキストをundoに登録 registerUndo(text: oldText) // oldTextを更新 oldText = textView.text } } }
modelとして切り離したうまくいかないコード
ViewController
1import UIKit 2 3class ViewController: UIViewController { 4 @IBOutlet weak var textView: UITextView! { 5 didSet { textView.delegate = self } 6 } 7 8 let textViewModel = TextViewModel() 9 10 // 画面の戻るボタンをタップ 11 @IBAction func undo(_ sender: Any) { 12 textViewModel.undo() 13 } 14 15 // 画面の進むボタンをタップ 16 @IBAction func redo() { 17 textViewModel.redo() 18 } 19 20} 21 22 23extension ViewController: UITextViewDelegate{ 24 // textViewの変更を検知 25 func textViewDidChange(_ textView: UITextView) { 26 if (textView.markedTextRange == nil) { 27 // modelのregisterUndoに現在のテキストビューの値を渡す 28 textViewModel.registerUndo(text: textView.text) 29 } 30 } 31} 32 33extension ViewController: EditorModelDelegate { 34 func changeTextView(text: String) { 35 // modelからの変更を反映 36 textView.text = text 37 } 38} 39
TextViewModel
1import Foundation 2 3// ViewControllerのテキストビューに反映させる為のデリゲート 4protocol EditorModelDelegate: AnyObject { 5 func changeTextView(text: String) 6} 7 8class TextViewModel { 9 let textViewUndoManager = UndoManager() 10 weak var delegate: EditorModelDelegate? 11 var oldText = "" 12 13 func registerUndo(text: String) { 14 if (textViewUndoManager.isUndoRegistrationEnabled) { 15 // undo登録処理 16 textViewUndoManager.registerUndo(withTarget: self, handler: { _ in 17 // ViewControllerから受け取った現在の値を登録してredoを実装 18 self.registerUndo(text: text) 19 // 現在の値より1操作分古い値をtextViewに反映させundoを実装 20 self.delegate?.changeTextView(text: self.oldText) 21 }) 22 } 23 // oldTextを更新 24 self.oldText = text 25 } 26 27 func undo() { 28 if(textViewUndoManager.canUndo){ 29 textViewUndoManager.undo() 30 } 31 } 32 33 func redo() { 34 if(textViewUndoManager.canRedo){ 35 textViewUndoManager.redo() 36 } 37 } 38}
現象
ABCと一文字ずつ入力して戻る操作を3回行った場合の想定の値は
AB -> A -> ""(空文字)
だが、
上記のコードで戻る操作を三回行うと
ABC -> AB -> A
のように一つ前の戻る操作の値がなぜか最新の値(この場合"ABC")になってしまう。
うまくいっているviewControllerに記載のコードと、うまくいかない分離したコードではやっていることは同じだと思うのですが、なぜこのような挙動になるのか分かりません。
いろんな行にprintを置いて確かめましたが、なぜかmodelに分離したパターンだとregisterUndoに登録される値が想定と異なります。
この現象の原因と解決方法をご教示いただきたいです。
あなたの回答
tips
プレビュー