SwiftUI View (apparently) laid out before init runs

好久不见. 提交于 2020-05-17 05:54:28

问题


TL;DR

It seems that the ContentView below evaluates the body's if statement before init has run. Is there a race condition, or is my mental model out-of-order?

Kudos

A shout-out to Asperi, who provided the state-initializer equivalent that solves today's problem.

Code

Why does ContentView display "dummy is nil"? It seems something gets closed over before the initializer sets dummy. What is it about the second assignment that fixes things?

class Dummy {  }

struct ContentView: View {
    @State private var dummy : Dummy?

    init() {
        print("Init") // In either case, is printed before "Body"

        // Using this assignment, "dummy is nil" shows on screen.
        self.dummy = Dummy()

        // Using this, "dummy is non-nil" shows on screen.
        // From https://stackoverflow.com/questions/61650040/swiftui-initializer-apparent-circularity
        // self._dummy = State(initialValue: Dummy())
    }

    var body: some View {
        print("Body")
        return ZStack {
            if dummy == nil {              // Decision seems to be taken
                Text("dummy is nil"    )   // before init() has finished.
            } else { 
                Text("dummy is non-nil") 
            }
        }
    }
}

回答1:


Consensus is that it's a feature that looks like a bug.

Helpful discussion in Swift Forum. Also here. Highlights (edited for clarity) include:

Good reasons not to do this in the first place:

  • You're not suppose to mutate during View init since that'd be in the body call of the parent view

  • @State variables in SwiftUI should not be initialized from data you pass down through the initializer; since the model is maintained outside of the view, there is no guarantee that the value will really be used.

  • Don't try overriding @State's initial value during init. It will only work once during very first view creation (important: not value initialization) and only if that view in the view tree gets re-created / replaced with a different view of the same type but internally different id.

The "it's a misfeature" position (close to mine):

  • This is due to an artificial limitation in the compiler's implementation of property wrappers. The workaround is to initialize the backing storage directly via _value

Explanations of how and why:

  • The value in @State will always be initialized with the value you pass in init, this is simple Swift. However before next body call SwiftUI will call update method and reinject a value into it if there was a previous value which will override your value from init.

  • This is NOT a bug! :man_facepalming: It works as expected/advertised. ... @State property wrapper adds one possible way of mutation to the view, which also is meant to be private to the view, not initialized from parent, etc.

It's pilot error (probably true):

  • There is NO problem here except people misunderstanding what State is build for.


来源:https://stackoverflow.com/questions/61661581/swiftui-view-apparently-laid-out-before-init-runs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!