はじめに
こちらの Swift Forums で Post している内容と同じです。まだ解決していないですが、現状をまとめておきます。
筆者環境
説明用コード
事象の説明のために下記のような簡易的なアプリケーションを考えます。
TextField に .onSubmit(of:_:) modifier を使っています。
import SwiftUI import Observation struct FirstView: View { @State private var isPresented: Bool = false var body: some View { Button("show") { isPresented = true } .sheet(isPresented: $isPresented) { SecondView() } } } struct SecondView: View { @State private var model = Model() @FocusState private var focusedField: Field? private enum Field: Hashable { case field1, field2 } var body: some View { Form { TextField("field 1", text: $model.field1) .focused($focusedField, equals: .field1) .onSubmit { focusedField = .field2 } TextField("field 2", text: $model.field2) .focused($focusedField, equals: .field2) } } } @Observable final class Model { var field1: String = "" var field2: String = "" deinit { print("DEINIT") } }
- FirstView から SecondView を sheet で表示
- SecondView では TextField が2つあり、1つ目の TextField の入力完了で 2つ目の TextField にフォーカスが移動する
- .onSubmit に書いた処理によるもの

さて、ここで sheet を閉じて SecondView のライフタイムが終了した時に、 SecondView が保持している Model クラスが破棄されることを期待します。
今回の実装だとデバッグコンソールに DEINIT が出力されるはずです。
が、実際には出力されず、メモリグラフを見ても Model クラスが生存していることが分かります。

結論、 .onSubmit が何か怪しいようで、 .onSubmit のクロージャを空にすると事象が再現しなくなります。
回避策
.onSubmit modifier を使わず、 TextField の deprecated な
init(_:text:onCommit:)を使う。
onCommit で onSubmit 相当の処理を行うことで、一旦は回避できるようです。
(deprecated なのでできれば使いたくないですが、、)
ref: https://developer.apple.com/forums/thread/780579
原因
.onSubmit が何か怪しくはあるのですが、原因は分かっていません。。
前述の通り、 Swift Forums にポストしていますが、本記事の執筆時点で特に反応はなく、理由は分かっていません。。
もし分かる方がいらっしゃったらぜひ教えてください 🙏
その他
最小限にしたコード
以下、説明用のコードよりも最小限にした再現コードを置いておきます。
Model クラスはプロパティすら持っておらず、保持しているだけです。
import SwiftUI import Observation struct FirstView: View { @State private var isPresented: Bool = false var body: some View { Button("show") { isPresented = true } .sheet(isPresented: $isPresented) { SecondView() } } } struct SecondView: View { private var model = Model() private let flag: Bool = true var body: some View { TextField("field", text: .constant("")) .onSubmit { _ = flag } } } @Observable final class Model { deinit { print("DEINIT") } }
その他の挙動
- TextField へのフォーカスを当てずに sheet を閉じると解放される
- 解放されなかった場合でも、再度 SecondView を開いて TextField にフォーカスを当てた段階で前回解放されなかった Model が解放される
- 解放されるタイミングこそ不可解ではあるが、解放されないインスタンスが増え続けるという感じではなさそう
- Model が Observation framework の @Observable でなく Combine の ObservableObject を利用した場合でも同様の事象が発生する
筆者の推測
メモリグラフを見た感じ、 .onSubmit は内部的に Environment の仕組みを使っているように見えました(多分)。そのため SwiftUI が管轄している Environment の値をストアしている箇所でキャプチャしているとかある、、?
おわりに
なかなかよく分からない挙動ですが、 TextField で onSubmit を使うのは割とあるユースケースな気がするので、皆さんどうされているんでしょうか、、? 筆者が何か使い方や解釈を間違えている可能性もあるので、情報ある方教えていただきたいです 🙏