2

Yes, I know this question has been asked before, but the accepted answers do not work for me. Those answers did indeed fix the compiler error, but when I pass 'self' to the initializer of a class from within my ContentView.init method, and that class later calls a method on ContentView, the 'self' is not the correct instance. The callback, setVpnState sets the value of a @State variable, which should cause the UI to update. But it doesn't, and on inspection in lldb, it is clearly not the same 'self' (instance of ContentView).

Here is what I tried to do initially:

struct ContentView: View {
    
    let iwinsController: IWiNSController

    init() {
        iwinsController = IWiNSController(view: self)
    }

    var body: some View {
        .
        .
        .
    }
        
    func setVpnState(_ state: ButtonState) {
        self.buttonAttributes = buttonConfig[state]!
    }
}

As you would expect, the error I got was 'self' used before all stored properties are initialized

I then tried both of the answers to this question: 'self' used before all stored properties are initialized

Which did satisfy the compiler, but as I said, when setVpnState is called from my controller object, 'self' is incorrect.

So, what I had to do was initialize my controller without passing 'self', and then in onAppear set the view explicitly. This works. But it means in my controller I have to unwrap view every time I use it.

It doesn't feel very 'Swifty'. I am new to Swift and would like to do this the accepted way.

struct ContentView: View {

    let iwinsController: IWiNSController

    init() {
       self.iwinsController = IWiNSController()
    }

    var body: some View {
        .
        .
        .
        }.onAppear {
            iwinsController.setView(view: self)
        }
    }
    
    func setVpnState(_ state: ButtonState) {
        self.buttonAttributes = buttonConfig[state]!
    }
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209
svenyonson
  • 1,815
  • 1
  • 21
  • 30
  • *pass 'self' to the initializer of a class* ... you think in class terms, but SwiftUI view is *a struct* - value type - there is no reference. This `iwinsController.setView(view: self) ` just makes a copy of `self`.... You need to change a mind for SwiftUI. – Asperi Nov 14 '20 at 21:48

1 Answers1

0

If I understand the question correctly it sounds like you want the view to track some vpnState class from your view and you're attempting to pass references from the view to the controller object, but that's backwards of how SwiftUI is designed.

Fundamentally you should view Views as read-only objects that are updated behind the scenes by the framework. You lock them onto a property or object to observe and when that thing changes the view is automatically updated with the new value.

@State variables are intended for use strictly within your views, like for use with an animation or some other intermediate state completely within the View. In this instance the Swifty thing to do would be to use a @ObservedObject or @EnvironmentObject to watch the state of the object for you.

As to why self is a "different" object when you use it that is because a struct is a value type not a reference type. It is destroyed and recreated by Swift each time it is change. It is not "updated" in the sense that values are changed on the object.

Joshua Olson
  • 3,675
  • 3
  • 27
  • 30
  • Also what Asperi said. Passing `self` to an object of a value type just duplicates the object which won't affect the original object anyway. – Joshua Olson Nov 14 '20 at 22:47
  • Thanks very much, I'll need to change my way of thinking, But, @Joshua - why does it then work when I call "setView" on my controller? If it is making a copy, why does setVpnState now work? – svenyonson Nov 14 '20 at 23:48
  • Would a more proper approach be to use Notification Center and send a notification from my controller object? What my intent was is to declutter the view from the mechanics of the application - have ContentView just be a UI. But state needs to be effected from other parts of the application. – svenyonson Nov 14 '20 at 23:51
  • Ok, just looked into @ObservedObject and will store state information in a separate class, so I will accept this answer – svenyonson Nov 15 '20 at 00:03
  • Your function is using self from within the object. It isn't passing it anywhere. – Joshua Olson Nov 15 '20 at 00:38
  • I pass ContentView.self to the controller in .onAppear, and then the controller calls the setVpnState method on the view (self) object is was passed – svenyonson Nov 15 '20 at 00:39
  • I'd say that it's working despite being wrong. By passing self you're creating a new view that has all of the same parameters as the original one, so it's likely there are two views overlapping each other and only one is getting set. – Joshua Olson Nov 15 '20 at 00:47
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/224570/discussion-between-svenyonson-and-joshua-olson). – svenyonson Nov 15 '20 at 01:08