質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

0回答

1014閲覧

【swift】UndoManagerのregisterUndoに登録した値が更新されてundo実行時とundo登録時の値に差異が出る

ichina

総合スコア6

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2020/12/29 07:56

前提・実現したいこと

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に登録される値が想定と異なります。

この現象の原因と解決方法をご教示いただきたいです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだ回答がついていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問