美文网首页Hacking with iOS: SwiftUI Edition
SwiftUI:属性包装器如何处理结构体

SwiftUI:属性包装器如何处理结构体

作者: 韦弦Zhy | 来源:发表于2020-06-01 19:08 被阅读0次

    \color{red}{\Large \mathbf{Hacking \quad with \quad iOS: SwiftUI \quad Edition}}

    {\Large \mathbf{Instafilter}}

    您已经了解了SwiftUI如何通过使用@State属性包装器将变化的数据存储在结构体中,如何使用$将状态绑定到UI控件的值,以及更改@state包装的属性时是如何自动让SwiftUI重新调用我们的结构体的body属性的。

    所有这些结合在一起,使我们可以编写如下代码:

    struct ContentView: View {
        @State private var blurAmount: CGFloat = 0
    
        var body: some View {
            VStack {
                Text("Hello, World!")
                    .blur(radius: blurAmount)
    
                Slider(value: $blurAmount, in: 0...20)
            }
        }
    }
    

    如果您执行这些代码,将会发现左右拖动滑块可以完全按照预期调整文本标签的模糊量。

    现在,假设我们希望该绑定不仅仅是处理模糊效果的半径。也许我们想将其保存到UserDefaults中,运行一个方法,或者只是打印出该值以进行调试。您可以尝试像这样更新属性:

    @State private var blurAmount: CGFloat = 0 {
        didSet {
            print("New value is \(blurAmount)")
        }
    }
    

    如果您运行该代码,您将感到失望:当您拖动滑块周围时,您会看到模糊量的变化,但是您不会看到我们的print()语句被触发——实际上,什么都不会输出。

    为了了解这里发生的事情,我希望您考虑一下我们在使用Core Data 时:我们使用@FetchRequest属性包装器查询我们的数据,但我还向您展示了如何直接使用FetchRequest结构体,以便我们可以更好地控制它是如何创建的。

    属性包装器具有该名称,因为它们将我们的属性包装在另一个结构体中。对于许多属性包装器而言,该结构体与包装器本身具有相同的名称,但是使用@FetchRequest时我向您展示了我们实际上是如何实际读取其中的包装值——获取的结果,而不是请求本身。 (参考:动态过滤

    这意味着当我们使用@State来包装字符串时,最终得到的实际属性类型是State<String>。类似地,当我们使用@Environment和其他环境时,我们最终得到一个Environment类型的结构体,该结构体内部包含一些其他值。

    之前我曾解释说,我们无法在视图中修改属性,因为它们是结构体,因此是固定的。但是,现在您知道@State本身会生成一个结构体,因此我们面临一个难题:如何修改该结构体?

    Xcode有一个非常有用的命令,称为“快速打开”(使用Cmd + Shift + O进行访问),该命令使您可以在项目或已导入的任何框架中找到任何文件或类型。现在将其激活,然后输入”State''——希望第一个结果在其下方显示SwiftUI,但如果没有,请找到并选择它。


    Open Quickly - State

    您将进入SwiftUI生成的界面,该界面实质上是SwiftUI向我们展示的所有的部分。那里没有实现代码,只有协议,结构体,修饰符等的许多定义。

    我们要求查看state,因此您应该被带到此行:

    @propertyWrapper public struct State<Value> : DynamicProperty {
    

    @propertyWrapper属性使它成为@State供我们使用。

    现在往下看几行,您应该看到以下内容:

    public var wrappedValue: Value { get nonmutating set }
    

    该包装值是我们要存储的实际值,例如字符串。这个生成的接口告诉我们,该属性可以读取(get)和写入(set),但是当我们设置该值时,它实际上不会更改结构体本身。在后台,它将值发送给SwiftUI以便存储在可以自由修改的位置,因此,结构体本身永不改变。

    现在您已经知道了所有这些,让我们回到我们的问题代码:

    @State private var blurAmount: CGFloat = 0 {
        didSet {
            print("New value is \(blurAmount)")
        }
    }
    

    在表面上,状态为“当blurAmount更改时,打印出它的新值。”但是,由于@State实际上会包装其内容,因此实际上是说,当包装blurAmountState结构体更改时,请打印出新的模糊量。

    还在这儿?现在让我们更进一步:您已经看到State如何使用一个非可变的setter包装其值,这意味着blurAmount或包装它的State结构体都没有改变——我们的绑定直接改变了内部存储的值,这意味着属性观察者永远不会被触发。

    那么我们该如何解决——我们如何将一些功能附加到包装的属性上?为此,我们需要自定义绑定——让我们接下来看看...

    译自 How property wrappers become structs

    相关文章

      网友评论

        本文标题:SwiftUI:属性包装器如何处理结构体

        本文链接:https://www.haomeiwen.com/subject/rwxfzhtx.html