Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use inout instead of Binding in SwiftUI?

Tags:

swift

swiftui

I like to know if I could make my view function bind itself via inout instead of Binding, is it possible in right/better syntax for us? right know I am getting compile time error of:

Modifying state during view update, this will cause undefined behavior.

which is understandable for me, I thought maybe I am using-wrong syntax for this work.

PS: I completely know about Binding and it use case, this question try find answer if we could do it with inout as well.


func TextView(inPutValue: inout Bool) -> some View {

    return Text("Hello")
        .onTapGesture { inPutValue.toggle() }
    
}

use case:

struct ContentView: View {

    @State private var isOn: Bool = true

    var body: some View {
        
        TextView(inPutValue: &isOn)
 
    }
    
}


update:

import SwiftUI

struct ContentView: View {

    @State private var value: Int = Int() { didSet { print(value.description) } }

    var body: some View {

        Button("update State via inout") { inoutFunction(incomingValue: &value) }
        
    }

}

func inoutFunction(incomingValue: inout Int) { incomingValue += 1 }
like image 248
ios coder Avatar asked Oct 18 '25 19:10

ios coder


1 Answers

The reason why you can't use inout here is because inout has a copy-in-copy-out behaviour. The value is passed into the function as a copy, and as soon as the function returns, the modified copy is copied back. You should not think of it as pass-by-reference, thought it can be implemented this way as an optimisation.

Now knowing that, it'd make a lot of sense for the Swift compiler to forbid you from using an inout parameter in an escaping closure, such as using inPutValue in onTapGesture. After all, the modified inPutValue is only copied back when TextView returns, not when someone taps it, so whatever modifications you do to it in onTapGesture is not visible to the caller of TextView at all. See also this answer.

So the value is copied back to the caller as soon as TextView returns. As the error message says, modifying a @State directly when computing body (i.e. "during view update") is not allowed.

Now let's look at the case of Button. Note that this time, the call to inoutFunction(incomingValue: &value) happens inside the button's click handler, rather than in body - meaning value will be written to when the button is pressed. This is allowed.

You can make your TextView to be of a similar form to Button's initialiser by adding a closure argument:

func TextView(update: @escaping () -> Void) -> some View {

    return Text("Hello")
        .onTapGesture(perform: update)
    
}

Note that there is nothing inout in this at all, just like there is nothing inout in Button.init.

You can then write your function with an inout parameter:

func inoutFunction(_ b: inout Bool) {
    b.toggle()
}

and use it:

@State private var isOn = true

var body: some View {
    TextView { inoutFunction(incomingValue: &isOn) }
}

Notice that you don't need inout at all.

TextView { isOn.toggle() }
like image 52
Sweeper Avatar answered Oct 20 '25 08:10

Sweeper