SwiftUI中的$ varName和_varName有什么区别

时间:2019-09-24 22:42:06

标签: ios swift swiftui

使用属性包装器时,您可以同时访问$varName_varName,但我并没有真正的区别。例如,这里

import SwiftUI

struct ContentView: View {
    @Binding var varName: String

    var body: some View {
        TextField("", text: $varName) //here you can also use `_varName`
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView(varName: .constant("Hello world!"))
  }
}
#endif

您可以同时使用$varName_varName。两种解决方案似乎是等效的。这两个变量均为Binding<String>。但是,如果我需要这样的东西:

import SwiftUI

struct ContentView: View {
    @Binding var varName: String

    init(varName: Binding<String>) {
        self.$varName = varName //ERROR
    }

    var body: some View {
        TextField("", text: $varName)
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView(varName: .constant("Hello world!"))
  }
}
#endif

我会得到一个错误:

  

无法分配给属性:“ $ varName”是不可变的

并且我必须使用_varName来抑制该错误:

struct ContentView: View {
    @Binding var varName: String

    init(varName: Binding<String>) {
        self._varName = varName //this works fine
    }

    var body: some View {
        TextField("", text: _varName)
    }
}

它们仍然都是Binding<String>,那么为什么以前的解决方案不起作用?根据苹果公司(https://developer.apple.com/videos/play/wwdc2019/415/的说法,编译器会将属性包装器转换为两部分。这个:

@Binding var varName: String

成为:

//Compiler-synthesized code
var $varName = Binding<String> = Binding<String>()

public var varName: String {
    get { $varName.wrappedValue }

    set { $varName.wrappedValue = newValue }
} 

$varName应该为var,那么为什么出现上述错误?而且,最重要的是,_varName是什么?它来自哪里?

1 个答案:

答案 0 :(得分:4)

合成的_varName属性是一个可存储的可设置属性,用于保存(在您的情况下)Binding<String>的实例。

varName属性映射到包装程序的wrappedValue属性。 Binding declares wrappedValue像这样:

var wrappedValue: Value { get nonmutating set }

由于wrappedValue是用nonmutating set声明的,因此合成的varName属性始终是可设置的(即使self不可变)。

如果包装器具有$varName属性,则将合成的projectedValue属性映射到包装器的projectedValue属性。 Binding declares projectedValue像这样:

var projectedValue: Binding<Value> { get }

由于projectedValue仅被声明为get,而不是get set,因此您永远不能分配给$varName

Binding不需要 来提供projectedValue属性,因为您可以使用_varName来获取Binding<String>对象。 Binding声明projectedValue属性的原因是使$前缀对Binding的作用与对StateObservedObject的作用相同,和EnvironmentObject