【问题标题】:SwiftUI call mutating func from var bodySwiftUI 从 var body 调用变异函数
【发布时间】:2019-11-05 08:50:13
【问题描述】:

在我关注的 Ray 教程中,我设置了以下属性

struct ContentView : View {

    var rTarget = Double.random(in: 0..<1)
    var gTarget = Double.random(in: 0..<1)
    var bTarget = Double.random(in: 0..<1)
}

这些当然是不可变的,所以我不能从 func 修改它们,除非我将该 func 标记为变异

func reset() {
    rTarget = Double.random(in: 0..<1)
    gTarget = Double.random(in: 0..<1)
    bTarget = Double.random(in: 0..<1)
}

Cannot assign to property: 'self' is immutable

但是我从var body调用这个函数

mutating func reset() {
    rTarget = Double.random(in: 0..<1)
    gTarget = Double.random(in: 0..<1)
    bTarget = Double.random(in: 0..<1)
}

fileprivate mutating func displayAlert() -> Alert {
    return Alert(title: Text("Your Score"), message: Text("\(computeScore())"), dismissButton: Alert.Button.destructive(Text("Ok"), onTrigger: {
        self.reset()
    }))
}

 var body: some View {
    Button(action: {
        self.showAlert = true
    }) {
        Text("Hit Me!")
        }.presentation($showAlert) {
            displayAlert()
    }
}

Cannot use mutating member on immutable value: 'self' is immutable

但我不能将var body 标记为mutating var

'mutating' may only be used on 'func' declarations

所以此时,我想在用户每次点击警报按钮时重置 xTarget 值,我不知道此时会有什么好的做法。

【问题讨论】:

    标签: ios swift swiftui xcode11


    【解决方案1】:

    我正在写同一篇文章。

    我没有遇到这个问题,因为我已经在使用 @State 属性包装器。 正如 kontiki 建议的那样,Session 226(通过 SwiftUI 的数据流)非常有助于了解在更新不同源的数据时如何使用哪个属性包装器。

    如果你想知道@State this answer 是什么,这一切都应有尽有。

    这是我的代码:

    @State var rTarget = Double.random(in: 0..<1)
    @State var gTarget = Double.random(in: 0..<1)
    @State var bTarget = Double.random(in: 0..<1)
    @State var rGuess: Double
    @State var gGuess: Double
    @State var bGuess: Double
    

    【讨论】:

      【解决方案2】:

      此时我找到的唯一解决方案是将 xTarget 属性标记为 @State 并在不改变函数的情况下修改它们

      @State private var rTarget = Double.random(in: 0..<1)
      @State private var gTarget = Double.random(in: 0..<1)
      @State private var bTarget = Double.random(in: 0..<1)
      

      但我不清楚这是一种好习惯。

      【讨论】:

      • 是的,这是大多数情况下的好习惯。 WWDC2019, Session 226 (Data Flow Through SwiftUI) 更详细地解释了它。
      【解决方案3】:

      很难说出在您的情况下推荐什么,因为在您的示例中,您没有向我们展示目标变量的实际用途。

      但是,我认为可以肯定地说,在 SwiftUI 视图中,需要随时间变化的变量必须是 @State 或您可用的绑定类型之一。否则,它很可能需要是不可变的。

      这一切都归结为确定什么是“真相的来源”。如果这些目标是视图内部的东西,则使用@State,如果事实来源在视图外部,则您将选择可绑定选项之一。

      我强烈建议您花 37 分钟观看 WWDC2019,第 226 场会议(通过 SwiftUI 的数据流)。它将清除您对此的所有问题。

      如果您赶时间,请跳到 5:45。但我确实建议你观看整件事。它最终会节省您的时间。

      如果您不知道“真相的来源”是什么。那么你一定要观看会议。

      【讨论】:

      • 另外,如果您有更多时间,我建议您查看 WWDC2019,第 415 届会议(现代 Swift API 设计)。它解释了属性包装器是什么以及它是如何工作的(即那些带有 @ 的属性,例如 @State@Binding 等.)。您甚至可以创建自己的。他们前面的美元符号 ($) 将开始变得有意义!这个你可以跳到 23:12。
      • 非常感谢你们,我明天会做这个。
      【解决方案4】:

      这个问题很老,但如果没有更多上下文,很难理解和理解答案。问题来自RayWenderlich - SwiftUI: Getting Started

      您需要做两件事才能在出现警报后重置游戏:

      1. 从你的函数displayAlert()中删除mutating
      2. 在要修改的变量前添加@State(即rTargetgTargetbTarget

      完整代码供参考 - 请注意,我使用func resetGame()重置游戏

      import SwiftUI
      
      struct ContentView: View {
          // In SwiftUI, when a @State variable changes,
          // the view invalidates its appearance and recomputes the body.
          @State var randomRed = Double.random(in: 0..<1)
          @State var randomGreen = Double.random(in: 0..<1)
          @State var randomBlue = Double.random(in: 0..<1)
      
          @State var redGuess: Double
          @State var greenGuess: Double
          @State var blueGuess: Double
      
          @State var showAlert: Bool = false
      
          func calculateScore() -> String {
              // The diff value is just the distance between two points in three-dimensional space.
              // You subtract it from 1, then scale it to a value out of 100.
              // Smaller diff yields a higher score.
              let redDiff = redGuess - randomRed
              let greenDiff = greenGuess - randomGreen
              let blueDiff = blueGuess - randomBlue
              let diff = sqrt(redDiff * redDiff + greenDiff * greenDiff + blueDiff * blueDiff)
      
              return "\(Int((1.0 - diff) * 100.0 + 0.5))"
          }
      
           func resetGame() {
              randomRed = Double.random(in: 0..<1)
              randomGreen = Double.random(in: 0..<1)
              randomBlue = Double.random(in: 0..<1)
      
              redGuess = 0.5
              greenGuess = 0.5
              blueGuess = 0.5
          }
      
          var body: some View {
              VStack {
                  HStack {
                      VStack {
                          Color(red: randomRed, green: randomGreen, blue: randomBlue)
                          Text("Match this color")
                      }
                      VStack {
                          Color(red: redGuess, green: greenGuess, blue: blueGuess)
                          Text("R: \(Int(redGuess * 255))  G: \(Int(greenGuess * 255))  B: \(Int(blueGuess * 255))")
                      }
                  }
      
                  Button(action: {self.showAlert = true} ) {
                      Text("Hit me")
                  }.alert(isPresented: $showAlert, content: {
                      Alert(title: Text("Your Score"), message: Text(self.calculateScore()),
                            dismissButton: Alert.Button.default(Text("OK"), action: { self.resetGame()
                      }))
                  }).padding()
      
                  ColorSlider(value: $redGuess, textColor: .red)
                  ColorSlider(value: $greenGuess, textColor: .green)
                  ColorSlider(value: $blueGuess, textColor: .blue)
              }
          }
      }
      
      struct ColorSlider: View {
          // Use @Binding instead of @State, because the ColorSlider view
          // doesn't own this data—it receives an initial value from its parent view and mutates it.
          @Binding var value: Double
          var textColor: Color
      
          var body: some View {
              HStack {
                  Text("0").foregroundColor(textColor)
                  Slider(value: $value)
                  Text("255").foregroundColor(textColor)
              }.padding(.horizontal)  // Add some space before & after the text
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2022-11-14
        • 2022-01-22
        • 1970-01-01
        • 2020-12-19
        • 2021-10-03
        • 2023-04-06
        • 2020-04-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多