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

我认为以下方法可以奏效:

struct AmountView : View {

    @Binding var amount: Double   
    @State var includeDecimal = false

    init(amount: Binding<Double>) {
        self.amount = amount
        self.includeDecimal = round(amount)-amount > 0
    }
}

然而,这给了我一个编译器错误如下:

不能将类型为“Binding”的值分配给类型为“Double”的值

我如何实现一个自定义init方法,在一个绑定结构?


当前回答

你可以用静态函数或者自定义init来实现。

import SwiftUI
import PlaygroundSupport

struct AmountView: View {
    @Binding var amount: Double
    @State var includeDecimal: Bool
    var body: some View {
        Text("The amount is \(amount). \n Decimals  \(includeDecimal ? "included" : "excluded")")
    }
}

extension AmountView {
    static func create(amount: Binding<Double>) -> Self {
        AmountView(amount: amount, includeDecimal: round(amount.wrappedValue) - amount.wrappedValue > 0)
    }
    init(amount: Binding<Double>) {
        _amount = amount
        includeDecimal = round(amount.wrappedValue) - amount.wrappedValue > 0
    }
}
struct ContentView: View {
    @State var amount1 = 5.2
    @State var amount2 = 5.6
    var body: some View {
        AmountView.create(amount: $amount1)
        AmountView(amount: $amount2)
    }
}

PlaygroundPage.current.setLiveView(ContentView())

实际上你根本不需要自定义init,因为逻辑可以很容易地移动到. onappear,除非你需要显式地在外部设置初始状态。

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

通过这种方式,您可以像文档建议的那样保持@State为私有并在内部初始化。

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

.

其他回答

你可以用静态函数或者自定义init来实现。

import SwiftUI
import PlaygroundSupport

struct AmountView: View {
    @Binding var amount: Double
    @State var includeDecimal: Bool
    var body: some View {
        Text("The amount is \(amount). \n Decimals  \(includeDecimal ? "included" : "excluded")")
    }
}

extension AmountView {
    static func create(amount: Binding<Double>) -> Self {
        AmountView(amount: amount, includeDecimal: round(amount.wrappedValue) - amount.wrappedValue > 0)
    }
    init(amount: Binding<Double>) {
        _amount = amount
        includeDecimal = round(amount.wrappedValue) - amount.wrappedValue > 0
    }
}
struct ContentView: View {
    @State var amount1 = 5.2
    @State var amount2 = 5.6
    var body: some View {
        AmountView.create(amount: $amount1)
        AmountView(amount: $amount2)
    }
}

PlaygroundPage.current.setLiveView(ContentView())

实际上你根本不需要自定义init,因为逻辑可以很容易地移动到. onappear,除非你需要显式地在外部设置初始状态。

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

通过这种方式,您可以像文档建议的那样保持@State为私有并在内部初始化。

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

.

既然现在是2020年年中,让我们来回顾一下:

关于@绑定金额

_amount建议只在初始化时使用。永远不要这样分配自己。初始化时$amount = XXX 量。wrappedValue和amount。projectedValue并不经常使用,但是您可以看到这样的情况

@Environment(\.presentationMode) var presentationMode

self.presentationMode.wrappedValue.dismiss()

@binding的一个常见用例是:

@Binding var showFavorited: Bool

Toggle(isOn: $showFavorited) {
    Text("Change filter")
}

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

在你的情况下:

init(amount: Binding<Double>) {
    _amount = amount
    includeDecimal = round(amount)-amount > 0
}

以下是苹果文件中的一段话:

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

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

你说(在评论中)“我需要能够改变includeDecimal”。改变includeDecimal意味着什么?显然,您希望根据amount(初始化时)是否为整数来初始化它。好的。如果includeDecimal为假,然后你把它改成真,会发生什么?你是否会迫使数量变成非整数?

总之,你不能在init中修改includeDecimal。但是你可以在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

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

公认的答案是一种方法,但还有另一种方法

struct AmountView : View {
var amount: Binding<Double>
  
init(withAmount: Binding<Double>) {
    self.amount = withAmount
}

var body: some View { ... }
}

删除@Binding并使其成为Binding类型的变量 棘手的部分是在更新这个变量时。你需要更新它的属性称为wrapped value。如

 amount.wrappedValue = 1.5 // or
 amount.wrappedValue.toggle()