Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI View with @State that is initialized in init() constructor

I have such a simple checkmark SwiftUI view. It is displayed in a List and can be toggled by tapping on it or can be toggled when refreshed from a data source (ex. Core Data)

My First implementation was

struct RoundedCheckmark: View {

    @State var isChecked : Bool
    let onCheck: (Bool) -> Void

    func toggle() { isChecked = !isChecked; onCheck(isChecked) }

    init(isChecked: Bool, onCheck: @escaping (Bool) -> Void = { _ in }) {
        self._isChecked = State(initialValue: isChecked)
        self.onCheck = onCheck
    }

    var body: some View {

        Button(action: toggle) {

            Image(isChecked ? "CheckedCheckmark" : "UncheckedCheckmark")
        }
    }
}

It seemed to work I could toggle it and it loads correctly. On toggle, I saved changed via onCheck closure/callback and it refreshed ok.

But after external refreshes like push notification refreshing underlying model in Core Data refresh of this View doesn't correctly change @State property isChecked.

In init() I am getting new value of isChecked and reinitialized @State with State(initialValue: ). But in the body, I am getting the old value of isChecked as from old @State property. so the view does have the wrong image.

Here is workaround i.e. relying only on let isChecked property and onCheck callback. But now I cannot have state inside my View, and I have to only rely on external data source change. Maybe it is more appropriate as this way I have a single source of truth without local @State storage.

struct RoundedCheckmark: View {

    let isChecked: Bool
    let onCheck: (Bool) -> Void

    func toggle() { onCheck(isChecked) }

    init(isChecked: Bool, onCheck: @escaping (Bool) -> Void = { _ in }) {

        self.isChecked = isChecked
        self.onCheck = onCheck
    }

    var body: some View {

        Button(action: toggle) {

            Image(isChecked ? "CheckedCheckmark" : "UncheckedCheckmark")

        }

    }
}
like image 262
Michał Ziobro Avatar asked Dec 06 '25 05:12

Michał Ziobro


1 Answers

isChecked as a @State variable is your source of truth. That means when some underlying data model (like CoreData) changes, it doesn't matter. Your view is only looking at the local @State version of isChecked.

But look at your view. To me, it shouldn't own its own state. Why? Because there is no semantic meaning to this view as a checkmark view. Its parent appears to own the state (hence why there is a onCheck callback). Instead, this should use a @Binding with no callback:

struct RoundedCheckmark: View {
    @Binding var isChecked: Bool

    var body: some View {
        Button(action: { isChecked.toggle() }) {
            Image(isChecked ? "CheckedCheckmark" : "UncheckedCheckmark")
        }
    }

Now your parent owns the state and you can infer its semantic meaning:

struct CheckmarkOwner: View {
    @State var showFavoritesOnly = false

    var body: some View {
        // content

        RoundedCheckmark(isChecked: $showFavoritesOnly)

        // and now something else will get notified when `showFavoritesOnly` gets toggled
        if showFavoritesOnly {
             // toggleable content
        }

        // more content
    }
}
like image 187
Procrastin8 Avatar answered Dec 07 '25 22:12

Procrastin8



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!