Difference between @Binding var a: Int and Binding<Int> in SwiftUI?

Viewed 995

I have a UIPageViewController that's wrapped up into a SwiftUI view. The view is instantiated with a @Binding value:

PageView([Text("Hello")], currentPage: $page)

I have some internal state that I'd like to change based on the incoming @Binding variable.

    @State var currentPageIndex: Int
    @State var goRight: Bool?
    
    public var currentPage: Binding<Int> {
        Binding(
            get: {
                return self.currentPageIndex
        },
            set: {
                self.goRight = ((self.currentPageIndex - $0) > 0)
                self.currentPageIndex = $0
        }
        )
    }

When I try to set the UIPageViewController based on this index,

pageViewController.setViewControllers([controllers[currentPage]], direction: .forward, animated: true)

this line fails with the error Cannot convert value of type 'Binding<Int>' to expected argument type 'Int'

Yet when I simply use a @Binding value without doing anything to it,

@Binding var currentPage: Int

everything compiles just fine. I understand the core of this issue to be, there's some sort of difference between @Binding var blah: Int and var blah: Binding<Int> Why is this the case? Why is there something else I have to do to access the underlying value of a type Binding<Int>? I understand these things to be two different ways of saying the same thing. Or are they not equivalent?

2 Answers
@Binding var blah: Int

becomes

var _blah: Binding<Int>
var blah: Int {
    get {
        _blah.wrappedValue
    }
    set {
        _blah.wrappedValue = newValue
    }
}

This means that if you have a @Binding var foo: Int and var bar: Binding<Int>, here's how you would do equivalent things:

struct MyView: View {
    //assume these are correctly initialized
    @Binding var foo: Int
    var bar: Binding<Int>

    var body: some View {
        //some body
    }

    func example() {
        // these are equivalent
        foo == bar.wrappedValue
        _foo == bar
    }
}

With

@Binding var blah: Int

blah is an Int and the @Binding is a property wrapper that projects a value via $blah, which is a type of Binding<Int>. So, they are not the same.

Likewise, @State property wrapper projects a Binding. So, you actually don't need to create a computed property, like you did with public var currentPage: Binding<Int>. You can just do:

self.$currentPageIndex

But, if you need to access the underlying value from Binding<T>, you can use the .wrappedValue property

Related