HennoTM
HennoTM

Reputation: 53

SwiftUI: Pass value to and use it in init of child view

I've been trying to create a small calendar app with SwiftUI and ran into some issues while trying to pass a value to a child view and use it in its init.

My code looks like this:

ContentView (parent view):

struct ContentView: View {
    @State var selectedMonth: Date
    
    var body: some View {
        MonthGridView(selectedMonth: $selectedMonth)
    }
}

MonthGridView (child view):

struct MonthGridView: View {
    @Binding private var selectedMonth: Date
    var days: [Int]
    
    //this is the part where I'm having troubles
    init(selectedMonth: Binding<Date>) {
        self._selectedMonth = selectedMonth
        days = dayIndices(currentMonth: $selectedMonth) //custom function, also in this line is the error right now
    }
    
    var body: some View {
        //code
    }
}

I have looked through a lot of posts on here and a wide variety of tutorials and this is what I came up with. I've tried moving some code around, but wasn't able to get it fully working. I imagine the problem is somewhere around the init, maybe about the Binding wrapper, but I was unable to find information about how to unwrap it.

Appreciate any help getting this working.

Upvotes: 0

Views: 1252

Answers (1)

rob mayoff
rob mayoff

Reputation: 385998

It'll be easier to understand the problem if we “de-sugar” the @Binding property wrapper. When you say this:

@Binding private var selectedMonth: Date

Swift translates that into this:

private var _selectedMonth: Binding<Date>

private var $selectedMonth: Date { _selectedMonth.projectedValue }

private var selectedDate: Date {
    get { _selectedMonth.wrappedValue }
    nonmutating set { _selectedMonth.wrappedValue }
}

Here is your init again:

init(selectedMonth: Binding<Date>) {
    self._selectedMonth = selectedMonth
    days = dayIndices(currentMonth: $selectedMonth) //custom function, also in this line is the error right now
}

You're using $selectedMonth before days has been initialized. But as I showed above, $selectedMonth is a computed property. You are not allowed to call any methods on self before self is fully initialized, and the getter of a computed property counts as a method.

In general, you can work around the limitation by accessing _selectedMonth.projectedValue directly:

    days = dayIndices(currentMonth: _selectedMonth.projectedValue)

However, in this case, all of _selectedMonth, _selectedMonth.projectedValue, and the init parameter selectedMonth are the same Binding, you can use any of them directly. So either of these will also work:

    days = dayIndices(currentMonth: selectedMonth)
    days = dayIndices(currentMonth: _selectedMonth)

Upvotes: 2

Related Questions