I am getting memory leak when using .onSubmit view modifier on TextField.
Here is a simplified reproducible example:
struct ListView: View {
var body: some View {
NavigationStack {
List(0..<100) { i in
NavigationLink("Select \(i)", value: i)
}
.navigationDestination(for: Int.self) { selection in
DetailView()
.navigationTitle("Page: \(selection)")
}
}
}
}
struct DetailView: View {
@State private var viewModel = DetailViewModel()
var body: some View {
TextField(
"",
text: $viewModel.text,
prompt: Text("Type in")
)
.padding()
.onSubmit {
print(viewModel.text)
}
}
}
@Observable
final class DetailViewModel {
var text: String = ""
deinit {
print("Deinit \(Self.self)")
}
}
Navigate to DetailView by selecting a row on ListView page and start typing in TextField, hit the submit button on keyboard and this DetailView with DetailViewModel will be leaked after navigating back to ListView, deinit will not be called. Commenting out .onSubmit {} part fixes the leak.
What I observed also, is that once I open the same page or other, It will create new DetailView instance with it's view model and previously leaked one will be released, possible indicating that system somehow holds the last active TextField until new one has become active.
I tried calling UIApplication.shared.resignFirstResponder() in onDissapear{} but this does not fix the leak.
Only way the leak is fixed, is by using deprecated TextField initializer:
init(_ titleKey: LocalizedStringKey, text: Binding<String>, onCommit: @escaping () -> Void)
where onCommit is essentially doing the same as onSubmit.