Selecting TextField Causes Application Hang on Device

I'm struggling with an elusive issue, where selecting a TextField, which then shows the onscreen keyboard, causes a later application hang, but only on an actual device (not in preview, not in the simulator). I've narrowed my code down to the simplest repro.

If you run the following code, you can try both the repro case and a case that avoids the issue, where the only difference between the two is whether you select a TextField, which displays the onscreen keyboard. Even if you just dismiss the keyboard, the hang still happens.

To repro, select the "Press Here To Show Keyboard To Cause Hang" TextField, then select the "Press Here Before Showing Keyboard To Not Hang" link, then follow through with Create New Group, Add Member, Create New Member. The app will hang when you select Create New Member.

If you start with the "Press Here Before Showing Keyboard to Not Hang", everything works. You can even select the Group Name TextField before selecting Add Member without issue.

I'm looking for any ideas or suggestions, thanks.

Here's the code:

import SwiftUI

class Member : Identifiable {
    let id = UUID()
    var name = ""
}

class Group : Identifiable {
    let id = UUID()
    var name = ""
    var members = [Member]()
    var memberIds = [UUID]()
}

struct MemberView : View {
    @Environment(\.dismiss) private var dismiss

    @Binding private var groups: [Group]
    @Binding private var members: [Member]
    
    @State private var member: Member

    init(groups: Binding<[Group]>, members: Binding<[Member]>, member: Member? = nil) {
        _groups = groups
        _members = members

        _member = .init(wrappedValue: member ?? Member())
    }
    
    var body: some View {
        Form {
            Section(header: Text("Member Data")) {
                TextField("Member Name", text: $member.name)
            }
        }
    }
}

struct MembersView : View {
    @Environment(\.dismiss) private var dismiss
    
    @Binding private var groups: [Group]
    @Binding private var members: [Member]
    
    init(groups: Binding<[Group]>, members: Binding<[Member]>) {
        _groups = groups
        _members = members
    }
    
    var body: some View {
        if members.isEmpty {
            NavigationLink("Create New Member") {
                MemberView(groups: $groups, members: $members)
            }
        }
        else {
            List(members) { member in
                Text(member.name)
            }
        }
    }
}

struct GroupView : View {
    @Binding private var groups: [Group]
    @Binding private var members: [Member]
    
    @State private var group = Group()
    @State private var selectedMemberId: UUID?
    
    init(groups: Binding<[Group]>, members: Binding<[Member]>) {
        _groups = groups
        _members = members
    }

    var body: some View {
        Form {
            Section(header: Text("Group Data")) {
                TextField("Group Name", text: $group.name)
            }
            
            Section(header: Text("Members")) {
                List(members) { member in
                    if group.memberIds.contains(member.id) {
                        NavigationLink {
                            MemberView(groups: $groups, members: $members, member: member)
                        }
                        label: {
                            Text(member.name)
                        }
                    }
                }
                
                NavigationLink {
                    MembersView(groups: $groups, members: $members)
                }
                label: {
                    Text("Add Member")
                }
            }
        }
    }
}

struct GroupsView : View {
    @Binding private var groups: [Group]
    @Binding private var members: [Member]
    
    init(groups: Binding<[Group]>, members: Binding<[Member]>) {
        _groups = groups
        _members = members
    }
    
    var body: some View {
        if groups.isEmpty {
            NavigationLink("Create New Group") {
                GroupView(groups: $groups, members: $members)
            }
        }
        else {
            List(groups) { group in
                Text(group.name)
            }
        }
    }
}

struct MainView : View {
    @State private var groups: [Group]
    @State private var members: [Member]
    @State private var settings: [String]
    @State private var setting = ""
    
    init() {
        _groups = .init(wrappedValue: [])
        _members = .init(wrappedValue: [])
        _settings = .init(wrappedValue: [])
    }

    var body: some View {
        NavigationStack {
            if settings.isEmpty {
                VStack {
                    TextField("Press Here To Show Keyboard To Cause Hang (Whether Or Not You Type Anything)", text: $setting) {
                        settings.append("Hang")
                    }

                    Button("Press Here Before Showing Keyboard To Not Hang") {
                        settings.append("No Hang")
                    }
                }
            }
            else {
                GroupsView(groups: $groups, members: $members)
            }
        }
    }
}

#Preview {
    MainView()
}

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            MainView()
        }
    }
}
Answered by Fat Xu in 828979022

Do not use 'dismiss'.

Accepted Answer

Do not use 'dismiss'.

Wow, you are totally correct. Simply removing the two

@Environment(\.dismiss) private var dismiss

lines makes the hang go away, despite the fact that I wasn't even invoking the dismiss instance.

Is this a known issue? The documentation says that this action is used to pop the current view from a NavigationStack.

I assume that my alternative is to use NavigationPath bindings instead?

To close the loop, I switched to using NavigationPath bindings that I pass through Views:

@Binding private var navigationPath: NavigationPath

// ...

// to dismiss:
if navigationPath.count > 0 {
    navigationPath.removeLast()
}
Selecting TextField Causes Application Hang on Device
 
 
Q