UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Assign value to @Binding var from subview's init

Forums > SwiftUI

I'm trying to set the value of a @Binding var in the init of a view. I undstand how to do that for @State variables...but is it possible to handle this for @Binding?

I get the error "Cannot assign value of type 'Int' to type 'Binding<Int>'"

Background: I have a view that lists entities from a Core Data fetch request. I have two versions of that list view based on whether the user has chosen to view a simple list or a list based on a segmented fetch request. The "sort by" menu is at the main view and I would like that menu disabled when the fetch request is empty or has a low number of items to display. I need to pass the entity count of the fetch request up a level via a binding but am having trouble with the binding syntax.

Thanks.

struct SimpleListView: View { //child view

    @FetchRequest(sortDescriptors: []) private var packets: FetchedResults<Packet>

    @Binding var returnCount: Int

    init(predicate: NSPredicate?, returnCount: Int) {
        let request: NSFetchRequest<Packet> = Packet.fetchRequest()
        request.sortDescriptors = []
        if let predicate = predicate {
            request.predicate = predicate
        }
        _packets = FetchRequest<Packet>(fetchRequest: request)

        _returnCount = 10 //temporarily set to static number to work out syntax
    }

2      

@Matt is trying to assign a value to a binding, but is pushing at the wrong end:

I get the error -> Cannot assign value of type 'Int' to type 'Binding<Int>'

Take a moment and step away from the keyboard. Grab a few 3x5 cards to help you visualize. Think of a laboratory with two light switches. Each switch can be ON or OFF. The laboratory is ONE card with a status panel. Draw the light switches on the other two cards.

If you change the value of one of the light switches, how to you send that value to the laboratory's main status panel? This is the job of the @Binding property wrapper.

You create a subview to model the light switch. When the value in the light switch changes, you want the new value reflected in your laboratory.

Paste into a new Xcode project:

struct UndergroundLaboratory: View {
    // Reference: Dr Doofenshmirtz
    // These are the states of your laboratory's devices.
    @State private var breadInator = true
    @State private var voiceInator = false

    var body: some View {
        VStack {
            Group {
                // This group is just display panel.
                Text("Bread-inator is \(breadInator ? "on." : "off.")")
                Text("Voice-inator is \(voiceInator ? "on." : "off.")")
            }.font(.largeTitle)
            Divider()
            VStack(spacing: 10) {
                // These two views are the controls for your devices.
                // Dollar signs mean: I pass a value to the subview.
                // But the subview can change it, if warranted.
                // If changed, I will update this view's @State variables.
                InatorSwitch(switchPosition: $breadInator, switchLabel: "Bread-inator")
                InatorSwitch(switchPosition: $voiceInator, switchLabel: "Voice-inator")
            }.padding(.horizontal)
        }
    }
}

// This is a specialised Lego brick. It has one purpose: mimic a light switch.
// It knows nothing about your laboratory, or its specialised equipment.
struct InatorSwitch: View {
    // Here binding says, someone else will pass me a boolean.
    // I am calling it switchPosition, don't care what others call it.
    // I may change this boolean, and will pass changes back up the line.

    @Binding var switchPosition: Bool  // Value passed in. You do NOT initialize here.
    var switchLabel: String
    var body: some View {
        // Dollar sign means: This value can change in this subview.
        Toggle(isOn: $switchPosition) {
            Text(switchLabel) // Some label
        }
    }
}

struct UndergroundLaboratory_Previews: PreviewProvider {
    static var previews: some View {
        UndergroundLaboratory()
    }
}

2      

A couple of points @Binding cannot be private as this value is recieved from another view and will pass any changes back to that view. Because of this then you should not set it in an init() as it initialised from the parent view.

This is a SubView that takes a Int from the ContentView

struct SubView: View {
    @Binding var returnCount: Int

    var body: some View {
        Text(String(returnCount))
            .padding()
            .background(.cyan)
            .cornerRadius(10)
            .onTapGesture {
                returnCount += 1
            }
    }
}
struct ContentView: View {
    @State private var returnCount = 20

    var body: some View {
        VStack {
            Text("returnCount from ContentView of \(returnCount)")
                .padding()

            SubView(returnCount: $returnCount)
        }
        .padding()
    }
}

If you want to use the Preview the SubView you can pass a .constant(10) (PS does not have to 10 but has to same type as Binding eg String, Double etc.)

struct SubView_Previews: PreviewProvider {
    static var previews: some View {
        SubView(returnCount: .constant(10))
    }
}

If this is unclear Paul also explains it What is the @Binding property wrapper?.

2      

Thanks @Obelix and @NigelGee for your responses.

I updated my example to correct the private @Binding.

In @NigelGee's example, the binding between the parent view's @State returnView and the child view's @Binding returnView is made under the hood when the subview is called with "SubView(returnCount: $returnCount)"

In many of Paul's examples he sets the value of an @State property wrapper in the view struct's init. In my example, I'm manipulating the @FetchRequest in the init based on an incoming NSPredicate. That part of the code works fine.

I'm wondering if there is a method/ syntax for manually setting up that binding and setting the value from within the init. The value I want to set it to (packets.count) isn't available before the child view initializes.

Something like:

struct SimpleListView: View { //child view

    @FetchRequest(sortDescriptors: []) private var packets: FetchedResults<Packet>

    @Binding var returnCount: Int

    init(predicate: NSPredicate?, parentViewReturnCount: Int) {
        let request: NSFetchRequest<Packet> = Packet.fetchRequest()
        request.sortDescriptors = []
        if let predicate = predicate {
            request.predicate = predicate
        }
        _packets = FetchRequest<Packet>(fetchRequest: request)

        _returnCount = parentViewReturnCount //Set child view binding to incoming @State var from parent view
        returnCount = packets.count //Set the binding var
    }

2      

Set the Binding amount from parent view as

@Binding var returnCount: Int

init(parentViewReturnCount: Binding<Int>) {
    _returnCount = parentViewReturnCount
}

Not sure that it then update returnCount with the packets.count will work in the init()

You might want to try some like this

.onChange(of: packets) { _ in
    returnCount = packets.count
}

2      

Thanks @NigelGee for your response.

The syntax you used creates the binding - but you are right that updating the value of returnCount in the init throws all sorts of new errors. (Modifying state during view update, this will cause undefined behavior.) I'll play a little more, but I probably need to refactor this to manage the data differently.

Thanks again.

2      

Got it to work with the help I received here.

Here's the solution:

struct SimpleListSeedsView: View {

    @FetchRequest(sortDescriptors: []) private var packets: FetchedResults<Packet>

    @Binding var returnPacketCount: Int

    init(predicate: NSPredicate?, packetsListed: Binding<Int>) {
        let request: NSFetchRequest<Packet> = Packet.fetchRequest()
        request.sortDescriptors = []
        if let predicate = predicate {
            request.predicate = predicate
        }
        _packets = FetchRequest<Packet>(fetchRequest: request)
        _returnPacketCount = packetsListed //This line sets the binding to the incoming @State var from parent view
    }

And in the child view:

            .onReceive(packets.publisher.count()) { _ in
                returnPacketCount = packets.count
             }

Here's a stackoverflow I found this morning with the same question: https://stackoverflow.com/questions/68673348/return-swiftui-fetchedresults-count-from-child-view-to-parent-when-fetchreques

Apple docs for .onReceive: https://developer.apple.com/documentation/swiftui/progressview/onreceive(_:perform:)/

Interestingly, .onReceive does not fire if the fetch request comes back empty.

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.