BLACK FRIDAY SALE: Save big on all my Swift books and bundles! >>

SOLVED: NavigationLink issue updating both views when item changes

Forums > SwiftUI

I have a view that creates a list for each item in my data model. One of the properties in the data model is a note, which is a string that I've marked with @Published

@Published var note = "" // Note for whole cycle

This is the view that creates the list. The CycleDetailView allows the user to change the note. Which works correctly, but when I go back to the list, the CycleSummaryView has the old note, it doesn't get updated. When I quit the app and start again, it has the changed value. How do I get the CycleSummaryView to notice?

struct CyclesView: View {
    @EnvironmentObject var store: PomoStore

    var body: some View {
        NavigationView {
            List($store.cycles) { $item in
                NavigationLink(destination: CycleDetailView(cycle: $item)) {
                    CycleSummaryView(cycle: $item)
                }

            }
            .navigationBarTitle("Pomodoro Cycles", displayMode: .inline)
        }
    }
}

struct CycleSummaryView: View {
    @Binding var cycle: PomoCycle

    var body: some View {
        VStack(alignment: .leading) {
            Text(cycle.note)
                .font(.headline)
            HStack {
                Text(cycle.beginTime, style: .date)
                    .font(.subheadline)
                Text(cycle.beginTime, style: .time)
                    .font(.subheadline)
            }
        }
    }
}

   

Bindings don't seem quite right here. I would expect updates in this case to be sent around with @Published, which you have already set up. What if you put @ObservedObject in place of @Binding in CycleSummaryiew?

   

@TravelByRocket seems right.

As you have a @Published property in the PomoCycle class (which is an ObservableObject) this isn't a simple struct (respectively a value type). @Binding should be used to get "mutating access" to value types (which is not the case in your CycleSummaryView anyways).

@ObservedObject should be used to track the changes of an ObservableObject. As mutating a class (reference type) is always possible in some way, there is not need for @Binding in this contex.

   

That worked! I don't really understand when to use @Binding or @ObservedObject, etc. Is there a good reference which explains when to use them?

   

I think there are a lot of explanations floating around out there and sometimes it's just a matter of finding the one the makes it click for you. I glanced at https://jaredsinclair.com/2020/05/07/swiftui-cheat-sheet.html and what stood out to me as a good shortcut/heuristic is that if it's @State in the parent view and you want to change it in a child view then use @Binding. If it is a class and the lifecycle of the object is managed elsewhere but you need to know when it changes so you can update your view then @ObservedObject would make sense.

   

Bryan gives good advice here:

I think there are a lot of explanations floating around out there and sometimes it's just a matter of finding the one the makes it click for you.

This is one way that it clicked for me.

When you're learning to program, most tutorials start with a ContentView. 🙄 I think this is a bad practice to call any view ContentView. To you, this is the whole program and somehow you think of @State as a way to control things in your entire program.

Instead, get a piece of paper and draw 8 mock iPhone screens for some fake application. One is the sign-up screen, one is a list of something, another is a preferences tab, another is detail screen with a rating button, one is a theme picker, etc, etc. Get into the habit of thinking about your application as a collection of many views. (By the way, NONE of these views should be called ContentView! Give them descriptive names, if you please!)

Now when you look at each of these 8 mock screens (views) think how each of them have different states. The sign-up screen might have a button disabled because the user did not complete the email field. The theme picker might be in a state where the user is selecting colors based on a palette, or on a color wheel. In the preferences view, the user may be looking at the privacy tab, instead of the avatar tab.

Each of these views have different states.

So think about @State as being a switch that can ONLY belong to a single view. @State tells you what's going on in a single view. It doesn't make sense (does it?) for the theme picker to know if the sign-up screen has incomplete password fields.

But what about bindings? @twostraws teaches a lesson where you create a subview (a ratings picker). You want that picker to change the value of an @State variable in a parent view. For example in your mock rating view, you have an @State variable named movieRating. In your theme picker view, you might have an @State variable named colorRating. In each case, you want the subview to display 1 to 5 stars, but you don't want it to update a variable in the ratingView, you want it to update the variable in the parent view. That is, it should update either movieRating, or colorRating. In this case you'll see how a binding is perfect. You have a local variable in the ratingView, but it is bound to the variable in the parent's view.

1      

I don't really understand when to use @Binding or @ObservedObject, etc. Is there a good reference which explains when to use them?

The guys at objc.io developed a nifty flow chart for figuring out which property wrapper to choose for your state needs. It was published in their book Thinking in SwiftUI but you can see it reproduced here: SwiftUI Property Wrappers

1      

@roosterboy That flowchart is the most helpful and the ugliest I've ever seen! Nice find

   

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.