【问题标题】:SwiftUI: How to implement a custom init with @Binding variablesSwiftUI:如何使用@Binding 变量实现自定义初始化
【发布时间】:2019-11-20 06:33:19
【问题描述】:

我正在处理资金输入屏幕,需要实现自定义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
    }
    ...
}

【问题讨论】:

    标签: swift swiftui


    【解决方案1】:

    啊!你是如此接近。这就是你的做法。你错过了一个美元符号(beta 3)或下划线(beta 4),或者在你的 amount 属性前面的 self ,或者在 amount 参数后面的 .value 。所有这些选项都有效:

    您会看到我删除了includeDecimal中的@State,请查看最后的说明。

    这是使用属性(把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
        }
    }
    

    或者在之后使用.value(但没有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(amount.value)-amount.value > 0
        }
    }
    

    这个是一样的,但是我们对参数(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
        }
    }
    
    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
        }
    }
    

    请注意,属性不需要 .value,这要归功于属性包装器 (@Binding),它创建了使 .value 变得不必要的访问器。但是,使用参数,没有这样的事情,你必须明确地做到这一点。如果您想了解有关属性包装器的更多信息,请查看WWDC session 415 - Modern Swift API Design 并跳转到 23:12。

    如您所见,从初始化程序修改 @State 变量将引发以下错误:线程 1:致命错误:访问 View.body 外部的状态。为避免这种情况,您应该删除@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 { ... }
    }
    

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

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

    【讨论】:

    • 谢谢!这有很大帮助!我在 self.includeDecimal = round(self.amount)-self.amount &gt; 0Thread 1: Fatal error: Accessing State&lt;Bool&gt; outside View.body 上遇到运行时错误
    • 嗯,这有点道理。 @State 变量应该代表事实的来源。但是在您的情况下,您正在复制该事实,因为 includeDecimal 的值可以从您的实际事实来源中得出,即数量。您有两个选择: 1. 将 includeDecimal 设为私有 var(无 @State),甚至更好 2. 将其设为从 amount 派生其值的计算属性。这样,如果金额发生变化,includeDecimal 也会发生变化。您应该像这样声明它:private var includeDecimal: Bool { return round(amount)-amount &gt; 0 } 并删除 self.includeDecimal = ...
    • 嗯,我需要能够更改includeDecimal,所以需要将它作为视图中的@State 变量。我真的只是想用一个起始值来初始化它
    • @Let's_Create 我只完整看了一遍,但感谢上帝提供了 forwards 按钮 ;-)
    • 非常好的解释,谢谢。我认为现在.value 已被.wrappedValue 取代,更新答案并删除测试版选项会很好。
    【解决方案2】:

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

    反正includeDecimal中的init是不能修改的。但是你可以在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
    

    (注意at some point $$includeDecimal 语法将更改为_includeDecimal。)

    【讨论】:

      【解决方案3】:

      自 2020 年年中以来,让我们回顾一下:

      至于@Binding amount

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

      2. amount.wrappedValueamount.projectedValue不常用,但可以看到像

      3. 这样的情况
      @Environment(\.presentationMode) var presentationMode
      
      self.presentationMode.wrappedValue.dismiss()
      
      1. @binding 的一个常见用例是:
      @Binding var showFavorited: Bool
      
      Toggle(isOn: $showFavorited) {
          Text("Change filter")
      }
      

      【讨论】:

        【解决方案4】:

        状态:

        To 管理您声明为 state 的任何属性的存储。当 state 值发生变化时,视图会使其外观无效并重新计算主体,您只能从视图主体内部或调用的方法访问 state 属性。 p>

        注意:要将状态属性传递给视图层次结构中的另一个视图,请使用带有 $ 前缀运算符的变量名。

        struct ContentView: View {
            @State private var isSmile : Bool = false
            var body: some View {
                VStack{
                    Text(isSmile ? "?" : "?").font(.custom("Arial", size: 120))
                    Toggle(isOn: $isSmile, label: {
                            Text("State")
                        }).fixedSize()
                }
            }
        }
        

        绑定:

        父视图声明一个属性来保存isSmile state,使用 State 属性包装器来指示该属性是该值的延迟视图的来源。

        struct ContentView: View {
            @State private var isSmile : Bool = false
            var body: some View {
                VStack{
                    Text(isSmile ? "?" : "?").font(.custom("Arial", size: 120))
                    SwitchView(isSmile: $isSmile)
                }
            }
        }
        

        使用绑定在存储数据的属性与显示和更改数据的视图之间创建双向连接。

        struct SwitchView: View {
            @Binding var isSmile : Bool
            var body: some View {
                VStack{
                        Toggle(isOn: $isSmile, label: {
                            Text("Binding")
                        }).fixedSize()
                }
            }
        }
        

        【讨论】:

          【解决方案5】:

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

          在你的情况下:

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

          这是来自 Apple 文档的引述:

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

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

          【讨论】:

            猜你喜欢
            • 2019-11-03
            • 2021-07-14
            • 2020-11-10
            • 1970-01-01
            • 2020-12-30
            • 2013-03-12
            • 2020-07-09
            • 1970-01-01
            相关资源
            最近更新 更多