文字っぽいの。

文字を書いています。写真も混ざります。

SwiftUI Introspect経由でDelegateを設定するとSwiftUIのBindingが死ぬ

結論

わからん。誰か詳しい人にどういう挙動が起きているのか教えてほしい。対応策を教えてもらえるともっと嬉しい。

書いてみたコード

画面にはTextEditorとその文字をクリアするButtonしかない非常にシンプルなもの。

import SwiftUI
@_spi(Advanced) import SwiftUIIntrospect

struct ContentView: View {
    @State var text: String = ""

    @Weak var textView: UITextView? {
        didSet {
            textView?.delegate = delegate
        }
    }

    var delegate = Delegate()

    final class Delegate: NSObject, UITextViewDelegate {
        func textViewDidChange(_ textView: UITextView) {
            print("Delegate:", textView.text)
        }
    }

    var body: some View {
        VStack {
            TextEditor(text: $text)
                .onChange(of: text, {
                    print(text)
                })
                .introspect(.textEditor, on: .iOS(.v16, .v17)){ textView in
                    self.textView = textView
                }
            Button {
                text = ""
            } label: {
                Text("文字クリア")
            }
        }
        .padding()
    }
}

起きること

textView?.delegate = delegateDelegateを渡すとしっかりと func textViewDidChange(_ textView: UITextView) が紐づいてそちらの処理は発火する。一方で、 .onChange(of: text) とButton内の text = "" は発火しなくなる。当然ながらtextView?.delegate = delegateコメントアウトしてやれば、.onChange(of: text)text = ""によるクリアも正しく動作する。

TextEditorの裏側にはUITextViewがおり、そのUITextViewDelegateを奪ってしまうから発火しないのだろうという推測はできるのだが……。

super. 的なコードを書いてどちらも発火させることはできないんだろうか? UITextViewDelegateがつなぎ込めてしまえば実装上は実現できるけれど、なんかね。