Home > Enterprise >  @Environment(\.dismiss) bug causes popped view to load a new version of itself in iOS 15
@Environment(\.dismiss) bug causes popped view to load a new version of itself in iOS 15

Time:09-23

Navigate to INCR: 3 and tap either the navigation bar back button or the dismiss button and you'll notice that same view is called again but this time it's a new version because the onAppear firstLoad = true and rand is a different value.

If you comment out @Environment(\.dismiss) var dismiss and dismiss() everything works as expected as it did in iOS 14. This issue also occurs with @Environment(\.presentationMode) var presentationMode

Not sure if this is a bug or if I'm making a silly mistake, but this issue is causing a ton of problems for my app because I have to be able to programmatically dismiss a view, so any input would be appreciated.

struct DetailView: View {
    
    @Environment(\.dismiss) var dismiss
    
    @State var isPresenting = false
    
    @State var incrInt = 0
    
    @State var firstLoad = true
    
    @State var rand = Int.random(in: 1..<500)
    
    var body: some View {
        
        Text("INCR: \(incrInt) RAND: \(rand)")
        
        Button("NAVIGATE"){
            isPresenting = true
        }
        Button("DISMISS"){
           dismiss()
        }
        
        .onAppear(perform: {
            
            if firstLoad{
                print("ON APPEAR FIRST LOAD")
                print(incrInt)
                print(rand)
                print("\n")
                firstLoad = false
            }
        })
        
        NavigationLink(destination: DetailView(incrInt: (incrInt   1)), isActive: $isPresenting){}
        
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView{
            VStack{
                DetailView()
            }
        }
    }
}

Video Link

https://i.imgur.com/qpu7NT7.mp4

Update Single Source of Truth

class DetailViewModel: ObservableObject {
    
    @Published var isPresenting = false
    
    var incr: Int
    
    var rand = Int.random(in: 1..<500)
    
    init(incr: Int){
        
        self.incr = incr
        
        print("INIT FIRST LOAD")
        print(incr)
        print(rand)
        print("\n")
    }
    
}

struct DetailView: View {
    
    @Environment(\.dismiss) var dismiss
    
    @StateObject var detailViewModel: DetailViewModel
    
    var body: some View {
        
        Text("INCR: \(detailViewModel.incr) RAND: \(detailViewModel.rand)")
        
        
        Button("NAVIGATE"){
            detailViewModel.isPresenting = true
        }
        Button("DISMISS"){
            dismiss()
        }
        
        
        NavigationLink(destination: DetailView(detailViewModel: DetailViewModel(incr: (detailViewModel.incr   1))), isActive: $detailViewModel.isPresenting){}
         
        
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView{
            VStack{
                DetailView(detailViewModel: DetailViewModel(incr: 0))
            }
        }
    }
}

CodePudding user response:

In your "Update" code, you are not using a single source of truth. You are creating and passing a new DetailViewModel into DetailView every time you click on the NavigationLink. Use only 1 DetailViewModel, and pass it around. In addition, you are changing isPresenting, so all your views that rely on this will be updated with the "new" value. This cascading is not what you want. Modify your logic. Using DetailViewModel is a good idea to keep the state of your model across views. Try something like this:

class DetailViewModel: ObservableObject {
   // @Published var isPresenting = false  // <-- not relevant
    
    var incr: Int
    var rand = Int.random(in: 1..<500)
    
    init(incr: Int) {
        self.incr = incr
        print("----> DetailViewModel init --> inc: \(incr) --> rand: \(rand) \n")
    }
    
    func doIncr(_ incr: Int) {
        self.incr = incr
        print("----> DetailViewModel doIncr --> inc: \(incr) --> rand: \(rand) \n")
    }
}

struct DetailView: View {
    @Environment(\.dismiss) var dismiss
    @ObservedObject var detailViewModel: DetailViewModel
    @State var showThyself = false  // <--- here
    
    var body: some View {
        Text("DetailView  INCR: \(detailViewModel.incr) RAND: \(detailViewModel.rand)")
        Button("NAVIGATE"){
            detailViewModel.doIncr(detailViewModel.incr   1)
            showThyself = true
        }
        Button("DISMISS"){
            dismiss()
        }
        NavigationLink(destination: DetailView(detailViewModel: detailViewModel), isActive: $showThyself){}
        .onAppear {
            // do something with the current state of your DetailViewModel
            print("----> DetailView onAppear \n")
        }
    }
}

struct ContentView: View {
    var detailViewModel = DetailViewModel(incr: 0)  // <--- here
    var body: some View {
        NavigationView{
            VStack{
                DetailView(detailViewModel: detailViewModel)
            }
        }
    }
}  
  • Related