SwiftUI Landmarks App Tutorial Screen Navigates Back When Toggle Favorite

Well, actually it is SwiftUI defect, the View being out of view hierarchy must not be refreshed (ie. body called) – it should be updated right after next appearance. (I submitted feedback #FB7659875, and recommend to do the same for everyone affected – this is the case when duplicates are better)

Meanwhile, below is possible temporary workaround (however it will continue work even after Apple fix the issue, so it is safe). The idea is to use local view state model as intermediate between view and published property and make it updated only when view is visible.

Provided only corrected view to be replaced in mentioned project.

Tested with Xcode 11.4 / iOS 13.4 – no unexpected “jump back”

demo

struct LandmarkList: View {
    @EnvironmentObject private var userData: UserData

    @State private var landmarks = [Landmark]() // local model
    @State private var isVisible = false        // own visibility state
    var body: some View {
        NavigationView {
            List {
                Toggle(isOn: $userData.showFavoritesOnly) {
                    Text("Show Favorites Only")
                }
                
                ForEach(landmarks) { landmark in
                    if !self.userData.showFavoritesOnly || landmark.isFavorite {
                        NavigationLink(
                            destination: LandmarkDetail(landmark: landmark)
                                .environmentObject(self.userData)
                        ) {
                            LandmarkRow(landmark: landmark)
                        }
                    }
                }
            }
            .onReceive(userData.$landmarks) { array in // observe external model
                if self.isVisible {
                    self.landmarks = array    // update local only if visible
                }
            }
            .onAppear {
                self.isVisible = true         // track own state
                self.landmarks = self.userData.landmarks
            }
            .onDisappear { self.isVisible = false } // track own state
            .navigationBarTitle(Text("Landmarks"))
        }
    }
}

Leave a Comment