3

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 }
ios coder
  • 1
  • 4
  • 31
  • 91

1 Answers1

5

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() }
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • WoW! thanks a lot, I just like you see my point of View why I was expecting my code works, because **onTapGesture** is changing the value that is not used in body as render reference, that was my reason that it should works, because I just past a State to **TextView** that has nothing to do in body at all, but after you explained it make all understandable for me now. – ios coder Apr 04 '21 at 10:51
  • is not this way a game changing? with this way we get access to direct modification without having getter and setters in between, don`t you think so? – ios coder Apr 04 '21 at 10:57
  • I'm not sure I understand. What "way" are you talking about? @swiftPunk – Sweeper Apr 04 '21 at 10:57
  • your answer I mean, we could make it in normal Binding way as well! – ios coder Apr 04 '21 at 10:58
  • @swiftPunk With a binding, you can abstract away the `toggle` call, i.e. the caller doesn't need to write the `isOn.toggle()` call every time. The caller just passes `$isOn`, and the callee knows how to deal with it (what value it should be set). – Sweeper Apr 04 '21 at 11:01