Any ideas how to help this Form + ForEach SwiftUI view update it’s NavigationLink Destination?

343 views Asked by At

I’m seeing this issue where a Form / NavigationLink set up seems to stop passing through the data on changes.

Expected behavior: Checkmarks should update when you pick a different food.
Observed behavior: You can see the favorite food changing outside the NavigationLink Destination, but not inside.

This setup mirrors a dynamic application where a ForEach is used to display various NavigationLinks in the Form based on parent data. Weirdly enough, this works if you replace Form with VStack, so I’m curious why this isn’t updating.

I have attached two minimum-setup example projects that replicate this issue where the destination of a NavigationLink is not receiving an update when data is changing. One with Binding, one with simpler passed properties.

Sample Project #1 with Binding - Dropbox
Sample Project #2 without Binding - Dropbox

Code #1:

//
//  ContentView.swift
//  Form Updating Example
//
//  Created by Sahand Nayebaziz on 12/10/20.
//

import SwiftUI

struct ContentView: View {
    @State var isPresentingMainView = false
    @State var favoriteFood: FoodType = .bagel
    
    var body: some View {
        VStack {
            Button(action: { isPresentingMainView = true }, label: {
                Text("Present Main View")
            })
        }
        .fullScreenCover(isPresented: $isPresentingMainView) {
            MainView(favoriteFood: $favoriteFood)
        }
    }
}

struct MainView: View {
    @Binding var favoriteFood: FoodType
    
    var body: some View {
        NavigationView {
            HStack {
                Spacer()
                Text(favoriteFood.emoji)
                    .font(.title)
                    .foregroundColor(.secondary)
                Spacer()
                NavigationView {
                    Form {
                        List {
                            ForEach(["SomethingRepresentingShowingFood"], id: \.self) { _ in
                                NavigationLink(
                                    destination: makeDetail(),
                                    label: {
                                        Text("Food Randomizer")
                                    })
                            }
                        }
                    }
                }
                .navigationViewStyle(StackNavigationViewStyle())
                .frame(maxWidth: 350)
            }
            .navigationTitle("Main")
            .navigationBarTitleDisplayMode(.inline)
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
    
    func makeDetail() -> some View {
        Form {
            ForEach(FoodType.allCases) { foodType in
                Button(action: { favoriteFood = foodType }, label: {
                    HStack {
                        Text(foodType.emoji)
                        Spacer()
                        if favoriteFood == foodType {
                            Image(systemName: "checkmark")
                        }
                    }
                })
            }
        }
    }
}

enum FoodType: String, Identifiable, CaseIterable {
    case bagel, pizza, broccoli
    
    var id: String { rawValue }
    
    var emoji: String {
        switch self {
        case .bagel: return ""
        case .pizza: return ""
        case .broccoli: return ""
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
            MainView(favoriteFood: .constant(.bagel))
            MainView(favoriteFood: .constant(.bagel))
                .makeDetail()
        }
    }
}

Code #2:

//
//  ContentView.swift
//  Form Updating Example
//
//  Created by Sahand Nayebaziz on 12/10/20.
//

import SwiftUI

struct ContentView: View {
    @State var isPresentingMainView = false
    @State var favoriteFood: FoodType = .bagel
    
    var body: some View {
        VStack {
            Button(action: { isPresentingMainView = true }, label: {
                Text("Present Main View")
            })
        }
        .fullScreenCover(isPresented: $isPresentingMainView) {
            MainView(currentFavoriteFood: favoriteFood, onUpdateFavoriteFood: { favoriteFood = $0 })
        }
    }
}

struct MainView: View {
    let currentFavoriteFood: FoodType
    let onUpdateFavoriteFood: (FoodType) -> Void
    
    var body: some View {
        NavigationView {
            HStack {
                Spacer()
                Text(currentFavoriteFood.emoji)
                    .font(.title)
                    .foregroundColor(.secondary)
                Spacer()
                NavigationView {
                    Form {
                        List {
                            ForEach(["SomethingRepresentingShowingFood"], id: \.self) { _ in
                                NavigationLink(
                                    destination: makeDetail(),
                                    label: {
                                        Text("Food Randomizer")
                                    })
                            }
                        }
                    }
                }
                .navigationViewStyle(StackNavigationViewStyle())
                .frame(maxWidth: 350)
            }
            .navigationTitle("Main")
            .navigationBarTitleDisplayMode(.inline)
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
    
    func makeDetail() -> some View {
        Form {
            ForEach(FoodType.allCases) { foodType in
                Button(action: { onUpdateFavoriteFood(foodType) }, label: {
                    HStack {
                        Text(foodType.emoji)
                        Spacer()
                        if currentFavoriteFood == foodType {
                            Image(systemName: "checkmark")
                        }
                    }
                })
            }
        }
    }
}

enum FoodType: String, Identifiable, CaseIterable {
    case bagel, pizza, broccoli
    
    var id: String { rawValue }
    
    var emoji: String {
        switch self {
        case .bagel: return ""
        case .pizza: return ""
        case .broccoli: return ""
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
            MainView(currentFavoriteFood: .bagel, onUpdateFavoriteFood: { _ in })
            MainView(currentFavoriteFood: .bagel, onUpdateFavoriteFood: { _ in })
                .makeDetail()
        }
    }
}
0

There are 0 answers