3

at Xcode's play ground I run code below:

import SwiftUI

struct Test {
    @State var count: Int = 0   //to avoid "mutating" keyword, I use "@State" property wrapper
    func countUp() {
        self.count += 1
    }
}

var test = Test()
test.countUp()     // I want "count" property to be count up. but it did not
print(test.count)  // print 0

Q) why test.count is still 0??

2
  • 1
    Expressions are not allowed at the top level, so how does that work at all? That being said, you can't access @State properties from outside the struct like that. You'll probably need to look into the @Published property wrapper. Commented Jun 5, 2021 at 10:39
  • what a simple & powerful comment! really appreciate it Commented Jun 5, 2021 at 14:58

2 Answers 2

1

This may not be the intent of the question, but I will explain why the code in question does not work.(I believe this is the same thing the comment.)

You are using SwiftUI in a playground and not View. The @State conforms to DynamicProperty.

https://developer.apple.com/documentation/swiftui/state https://developer.apple.com/documentation/swiftui/dynamicproperty

The Documents say:

You should only access a state property from inside the view’s body, or from methods called by it.

An interface for a stored variable that updates an external property of a view. and The view gives values to these properties prior to recomputing the view’s body.

It has been suggested to use it with View and view’s body.

As mentioned above, your code does not use View or view’s body.

Since @State can only be used in a struct, if you want to do something similar, you can use a class and use @Published as shown below(simple use Combine, not SwiftUI):

import Combine

class Test {
    @Published var count: Int = 0
    func countUp() {
        self.count += 1
    }
}

var test = Test()
test.countUp()
print(test.count) // 1

However, your code has no one to subscribe to the changes, so this is just an experimental code.

Sign up to request clarification or add additional context in comments.

Comments

1

Expressions are not allowed at the top level.

To test your case I created a View struct called Test1, and within its initialiser I created object for Test view. Check code below.

import SwiftUI

struct Test1:View {

    init() {
        let test = Test() // concentrate on this
        test.countUp()     
    }

    var body: some View {
        Test() // Ignore for now
    }
}

Test View-:

import SwiftUI


@propertyWrapper struct TestCount:DynamicProperty {
    @State private var count = 0
    var wrappedValue: Int {
        get {
            count
        }
        nonmutating set {
           count = newValue
        }
    }

    init(_ count: Int) {
        self.count = count
    }
}

struct Test: View {

    @TestCount var count:Int
    
    init(count:Int) {
        _count = TestCount(count)
        print(_count)
    }
    
    var body: some View {
        Button {
            countUp()
        } label: {
            Text("\(count)")
        }
    }

     func countUp()  {
        count += 1
        print(count)
    }

}

For better understanding I created a custom propertyWrapper and we will initialise that inside Test view.

Now, when you run this code in simulator Test1 view is called first, and in it’s initialiser it will create object for Test() view.

Test view upon initialisation will create object for it’s property wrapper called TestCount, once created it will print description for TestCount instance, and notice the print output now-:

TestCount(_count: SwiftUI.State<Swift.Int>(_value: 0, _location: nil))

You can clearly see State variable count inside TestCount wrapper was assigned default value 0, but wasn’t allocated any SwiftUI.StoredLocation yet.

Now, this line is executed next test.countUp() in Test1 view, and because we are calling it from outside of body property and Test1 is also initialised outside of body property , compiler is smart enough to detect that and it will not provide SwiftUI storage location for this change to count variable, because there is no view update required for this change, hence you always see Output as 0 (default saved value).

To prove my point, now make following change in Test1 view.

import SwiftUI

struct Test1:View {

//    init() {
//        let test = Test(count: 0)
//        test.countUp()     // I want "count" property to be count up. but it did not
//    }

    var body: some View {
        Test(count: 0)
    }
}

Now again the initial print statement inside Test view is same -:

TestCount(_count: SwiftUI.State<Swift.Int>(_value: 0, _location: nil))

But, now you have a view with Button to change the count variable value on tap.

When you tap the button, again countUp() will be called, and this time check the output of print statement again, SwiftUI storage location is no more nil.

TestCount(_count: SwiftUI.State<Swift.Int>(_value: 0, _location: Optional(SwiftUI.StoredLocation<Swift.Int>))).

You can debug the code further to get more better understanding, and I would also suggest you to read about PropertyWrappers in more detail, and how they work internally.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.