SwiftUI:如何使用@Binding变量实现自定义init

kee*_*n3d 21 swift swiftui

我正在用钱输入屏幕,需要实现一个自定义项,init以根据初始化的金额设置状态变量。

我以为这可以用,但是出现编译器错误:

Cannot assign value of type 'Binding<Double>' to type 'Double'

struct AmountView : View {
    @Binding var amount: Double

    @State var includeDecimal = false

    init(amount: Binding<Double>) {
        self.amount = amount
        self.includeDecimal = round(amount)-amount > 0
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

Sim*_*ang 38

您应该使用下划线来访问属性包装器本身的合成存储。

\n

在你的情况下:

\n
init(amount: Binding<Double>) {\n    _amount = amount\n    includeDecimal = round(amount)-amount > 0\n}\n
Run Code Online (Sandbox Code Playgroud)\n

以下是苹果文档中的引用:

\n
\n

编译器通过在包装属性名称前添加下划线 (_)\xe2\x80\x94 来合成包装类型实例的存储,例如,someProperty 的包装存储为 _someProperty。包装器的综合存储具有私有的访问控制级别。

\n
\n

链接: https: //docs.swift.org/swift-book/ReferenceManual/Attributes.html -> propertyWrapper 部分

\n


kon*_*iki 32

啊!你好亲近 这就是你的做法。您错过了美元符号(测试版3)或下划线(测试版4),并且在您的amount属性前面输入了self,或者在amount参数之后输入了.value。所有这些选项均有效:

您会看到我删除了@StateincludeDecimal中的内容,最后检查了说明。

这是使用属性(将self放在其前面):

struct AmountView : View {
    @Binding var amount: Double

    private var includeDecimal = false

    init(amount: Binding<Double>) {

        // self.$amount = amount // beta 3
        self._amount = amount // beta 4

        self.includeDecimal = round(self.amount)-self.amount > 0
    }
}
Run Code Online (Sandbox Code Playgroud)

或之后使用.value(但不使用self,因为您使用的是传递的参数,而不是struct的属性):

struct AmountView : View {
    @Binding var amount: Double

    private var includeDecimal = false

    init(amount: Binding<Double>) {
        // self.$amount = amount // beta 3
        self._amount = amount // beta 4

        self.includeDecimal = round(amount.value)-amount.value > 0
    }
}
Run Code Online (Sandbox Code Playgroud)

相同,但是我们对参数(withAmount)和属性(amount)使用不同的名称,因此您可以清楚地看到使用它们的时间。

struct AmountView : View {
    @Binding var amount: Double

    private var includeDecimal = false

    init(withAmount: Binding<Double>) {
        // self.$amount = withAmount // beta 3
        self._amount = withAmount // beta 4

        self.includeDecimal = round(self.amount)-self.amount > 0
    }
}
Run Code Online (Sandbox Code Playgroud)
struct AmountView : View {
    @Binding var amount: Double

    private var includeDecimal = false

    init(withAmount: Binding<Double>) {
        // self.$amount = withAmount // beta 3
        self._amount = withAmount // beta 4

        self.includeDecimal = round(withAmount.value)-withAmount.value > 0
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,由于属性包装器(@Binding)创建了不需要使用.value的访问器,因此属性不需要.value。但是,有了参数,就没有这种事情了,您必须显式地进行操作。如果您想了解有关属性包装器的更多信息,请查看WWDC会话415-Modern Swift API Design并跳至23:12。

如您所见,从初始化程序修改@State变量将引发以下错误:线程1:致命错误:在View.body之外访问State。为了避免这种情况,您应该删除@State。这是有道理的,因为includeDecimal不是事实的来源。其值是从金额中得出的。但是,删除@State includeDecimal不会在金额更改时更新。为此,最好的选择是将includeDecimal定义为计算属性,以便其值源自真值(量)来源。这样,每当金额更改时,您的includeDecimal也会更改。如果您的视图依赖于includeDecimal,则它应在更改时更新:

struct AmountView : View {
    @Binding var amount: Double

    private var includeDecimal: Bool {
        return round(amount)-amount > 0
    }

    init(withAmount: Binding<Double>) {
        self.$amount = withAmount
    }

    var body: some View { ... }
}
Run Code Online (Sandbox Code Playgroud)

rob mayoff所示,您还可以使用$$varName(beta 3)或_varName(beta4)初始化State变量:

// Beta 3:
$$includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)

// Beta 4:
_includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)
Run Code Online (Sandbox Code Playgroud)

  • 非常好的解释,谢谢。我认为现在 `.value` 已替换为 `.wrappedValue`,最好更新答案并删除 beta 选项。 (6认同)
  • 呃,斯威夫特真是一团糟。2023 年 6 月的答案是什么? (2认同)

rob*_*off 12

你说(在评论中)“我需要能够改变includeDecimal”。改变是什么意思includeDecimal?您显然想根据amount(在初始化时)是否为整数来初始化它。好的。那么如果includeDecimalfalse然后你把它改成会发生true什么?你会以某种方式强制amount然后成为非整数吗?

无论如何,你不能includeDecimalinit. 但是你可以在 中初始化它init,像这样:

struct ContentView : View {
    @Binding var amount: Double

    init(amount: Binding<Double>) {
        $amount = amount
        $$includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)
    }

    @State private var includeDecimal: Bool
Run Code Online (Sandbox Code Playgroud)

(请注意,在某些时候$$includeDecimal语法将更改为_includeDecimal。)


Jac*_*cky 5

由于是 2020 年年中,让我们回顾一下:

至于 @Binding amount

  1. _amount建议只在初始化期间使用。并且self.$amount = xxx在初始化期间永远不要像这样分配

  2. amount.wrappedValue并且amount.projectedValue不经常使用,但您可以看到类似的情况

@Environment(\.presentationMode) var presentationMode

self.presentationMode.wrappedValue.dismiss()
Run Code Online (Sandbox Code Playgroud)
  1. @binding 的一个常见用例是:
@Binding var showFavorited: Bool

Toggle(isOn: $showFavorited) {
    Text("Change filter")
}
Run Code Online (Sandbox Code Playgroud)


Pau*_*l B 5

您可以使用静态函数或自定义 init 来实现此目的。

\n
import SwiftUI\nimport PlaygroundSupport\n\nstruct AmountView: View {\n    @Binding var amount: Double\n    @State var includeDecimal: Bool\n    var body: some View {\n        Text("The amount is \\(amount). \\n Decimals  \\(includeDecimal ? "included" : "excluded")")\n    }\n}\n\nextension AmountView {\n    static func create(amount: Binding<Double>) -> Self {\n        AmountView(amount: amount, includeDecimal: round(amount.wrappedValue) - amount.wrappedValue > 0)\n    }\n    init(amount: Binding<Double>) {\n        _amount = amount\n        includeDecimal = round(amount.wrappedValue) - amount.wrappedValue > 0\n    }\n}\nstruct ContentView: View {\n    @State var amount1 = 5.2\n    @State var amount2 = 5.6\n    var body: some View {\n        AmountView.create(amount: $amount1)\n        AmountView(amount: $amount2)\n    }\n}\n\nPlaygroundPage.current.setLiveView(ContentView())\n
Run Code Online (Sandbox Code Playgroud)\n

实际上,您根本不需要在这里进行自定义初始化,因为.onAppear除非您需要在外部显式设置初始状态,否则可以轻松移动逻辑。

\n
struct AmountView: View {\n    @Binding var amount: Double\n    @State private var includeDecimal = true\n    \n    var body: some View {\n        Text("The amount is \\(amount, specifier: includeDecimal ? "%.3f" : "%.0f")")\n        Toggle("Include decimal", isOn: $includeDecimal)\n            .onAppear {\n                includeDecimal = round(amount) - amount > 0\n            }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这样,您就可以将 @State 保持私有并按照文档的建议在内部进行初始化。

\n
\n

不要在视图层次结构中实例化视图的位置初始化视图的状态属性,因为这可能与 SwiftUI 提供的存储管理发生冲突。为了避免这种情况,\n始终将状态声明为私有,并将其放置在\n需要访问该值的视图层次结构中的最高视图中

\n
\n

\n