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

sup*_*cio 1 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
Run Code Online (Sandbox Code Playgroud)

您可以同时使用$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
Run Code Online (Sandbox Code Playgroud)

我会得到一个错误:

无法分配给属性:“$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)
    }
}
Run Code Online (Sandbox Code Playgroud)

They are still both Binding<String>, so why won't the former solution work? According to Apple (https://developer.apple.com/videos/play/wwdc2019/415/) the compiler will turn a property wrapper into two things. This:

@Binding var varName: String
Run Code Online (Sandbox Code Playgroud)

becomes:

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

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

    set { $varName.wrappedValue = newValue }
} 
Run Code Online (Sandbox Code Playgroud)

$varName should be var, so why the error above? And, above all, what is that _varName? Where does it come from?

rob*_*off 5

合成_varName属性是一个存储的、可设置的属性,它保存(在您的情况下)Binding<String>.

varName属性映射到包装器的wrappedValue属性。Binding声明wrappedValue如下:

var wrappedValue: Value { get nonmutating set }
Run Code Online (Sandbox Code Playgroud)

因为wrappedValue是用 声明的nonmutating set,所以合成的varName属性总是可设置的(即使self是不可变的)。

如果包装器具有属性,则合成$varName属性将映射到包装projectedValue器的projectedValue属性。Binding声明projectedValue如下:

var projectedValue: Binding<Value> { get }
Run Code Online (Sandbox Code Playgroud)

由于projectedValue只是声明get,而不是get set,你永远不能分配给$varName.

Binding并不需要提供一个projectedValue属性,因为你可以使用_varName来获取Binding<String>对象。究其原因Binding声明一个projectedValue属性,使$前缀工作以同样的方式Binding,因为它确实为StateObservedObjectEnvironmentObject