Swiftui Landmarks App Tutorial Screen Navigates Back When Toggle Favorite

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"))
}
}
}

Why does SwiftUI automatically navigate backwards when my underlying data source is updated and how can I avoid this behavior?

Just remove the id from the ForEach and it'll stop navigating back

ForEach(viewModel.list) { item in
NavigationLink(destination: DemoDetail(viewModel: self.viewModel, item: item)) {
Text(item.description)
}
}

Toggling Property Does Not Show Change in Real Time

Your item array contains structs. Structs in Swift are value types. You effectively assign a copy of the struct to currentItem when you say var currentItem = item[currentItemIndex!]. This copy is then modified and thrown away when the function returns.

Later, when you access data[currentItemIndex!] in setStarButton you are accessing the original, unmodified struct. After you refresh the array from Firebase you see the correct value.

A simple way to avoid the problem is just to eliminate the local variable currentItem and operate on the struct in the array directly.

Personally, I would rater pass the relevant value to setStarred; that way you eliminate the duplicated code of access the items array. You should also avoid force unwrapping wherever possible. It also doesn't make a lot of sense for properties like isStarred and docID to be optional - An item is either starred or it isn't. A default value of false makes more sense than nil. All documents from Firebase will have a docID

func setStarButton(starred: Bool) {

let imageName = starred ? "star.fill" : "star"

starButton.setImage(UIImage(systemName: imageName), for: .normal)

}

@IBAction func starTapped(_ sender: Any) {
guard itemIndex = self.currentItemIndex else {
return
}

var isStarred = item[itemIndex].isStarred ?? false

isStarred.toggle()
item[itemIndex].isStarred = isStarred

// Update the database
model.updateStarredStatus(currentItem.docID!, isStarred)

// Update the button
setStarButton(starred: isStarred)

}

override func viewDidAppear(_ animated: Bool) {
// Set the status of the star button
if let itemIndex = self.currentItemIndex {
setStarButton(starred: self.item[itemIndex].isStarred ?? false)
}
}



Related Topics



Leave a reply



Submit