swiftswiftui

App freezes using @Environment(\.dismiss) variable, SwiftUI


The whole app is working fine and code is compiling perfectly, but when I navigate from ServiceListView to EmployeeDetailView and press any button, the CPU goes to 100% and the app freezes.

This solution SwiftUI NavigationBarItems slideBack freezes app does not work in my case.

If I remove this @Environment(\.dismiss) private var dismiss from my ServicesListView view then code works fine. Any reason for it?

Similar Question is posted on Apple Forum @Environment(.dismiss) var dismiss - Infinite loop bug

enter image description here

Service List View

struct ServicesListView: AppTNowView {
    
    var services: [Service] = []
    var isAvailableNowView = false
    
    @State private var moveToEmployeeDetail = false
    @State private var isCallingFirstTime = true
    
    @StateObject var viewModel =  ServicesListViewModel()
    @Environment(\.dismiss) private var dismiss
    
    var body: some View {
        ZStack {
            loadView
            LoaderView(isLoading: $viewModel.showLoader)
        }.alert(isPresented: $viewModel.showError, content: {
            Alert(title: Text(viewModel.errorMessage))
        }).onAppear{
            if isCallingFirstTime {
                viewModel.selectedServiceID = services.first?.id ?? 0
                viewModel.selectedService = services.first
                fetchEmployee()
                isCallingFirstTime.toggle()
            }
            
        }.onChange(of: viewModel.selectedServiceID, perform: { newValue in
            debugPrint("Appt: selected index \(newValue)")
            viewModel.selectedServiceID = newValue
            viewModel.selectedService = services.first(where: {$0.id == newValue})
            fetchEmployee()
        })
    }
}

Code for performing navigation into the ServiceListView.

ScrollView {
    if viewModel.employeeList.isEmpty {
        EmptyListView(imageName: ImageName.Home.noEmployee.rawValue, descriptionText: "No Employees available against this service")
            .padding(.top, 50)
    } else {
        VStack {
            ForEach(viewModel.employeeList) { employee in
                NavigationLink {
                    EmployeeDetailView(employee: employee)
                        .hideNavigationBar
                } label: {
                    EmployeeInfoCardView(employee: employee)
                }
            }
        }
    }
}

Here is my EmployeeDetailView

struct EmployeeDetailView: AppTNowView {
    
    @State private var isCalanderPresented = false
    @StateObject var viewModel = EmployeeDetailViewModle()
    @FocusState private var isMessageFocused: Bool
    @Environment(\.dismiss) private var dismiss
    var employee: Employee
    
    var body: some View {
        ZStack {
            loadView
            LoaderView(isLoading: $viewModel.showLoader)
        }
        .alert(isPresented: $viewModel.showError, content: {
            Alert(title: Text(viewModel.errorMessage))
        })
        .sheet(isPresented: $isCalanderPresented) {
            CalanderView(date: $viewModel.selecteDate, isCalanderPresented: $isCalanderPresented)
                .presentationDetents([.medium, .large])
                .onDisappear {
                    
                }
        }
    }
}

Solution

  • Unfortunately you might need to fallback to using the older @Environment(\.presentationMode) private var presentationMode in the meantime.

    I've been encountering the same issue with iOS 16/17 since moving to NavigationStack and I cannot pinpoint what is causing the infinite loop.

    I have a sheet and push a couple views via NavigationLink, in which the app freezes when presenting the second consecutive NavigationLink. Classic SwiftUI.

    Another two other alternative approaches, if you omit dismiss and don't want to use presentationMode;

    1. if you can get a reference to the active view controller, you can directly call .dismiss(animated: animated) to immediately dismiss the VC
    2. if you want to pop rather than dismiss, get a reference to the VC and call let nc = vc?.children.first as? UINavigationController to get the child navigation controller, and then nc?.popViewController(animated: true) to pop it one view back.