SwiftUI:如何避免在视图更新期间修改状态?

时间:2019-08-10 14:10:06

标签: swift uilabel swiftui

我想在按下文本标签后更新文本标签,但是我的应用程序运行时出现此错误(没有编译错误):[SwiftUI]在视图更新期间修改状态,这将导致不确定的行为。< / p>

这是我的代码:

import SwiftUI

var randomNum = Int.random(in: 0 ..< 230)

struct Flashcard : View {

    @State var cardText = String()

    var body: some View {    
      randomNum = Int.random(in: 0 ..< 230)
      cardText = myArray[randomNum].kana      
      let stack = VStack {        
        Text(cardText)
          .color(.red)
          .bold()
          .font(.title)
          .tapAction {
            self.flipCard()
          }
      }     
      return stack
    }

   func flipCard() {
      cardText = myArray[randomNum].romaji
   }   
}

3 个答案:

答案 0 :(得分:2)

struct ContentView: View {
    @State var cardText: String = "Hello"
    var body: some View {
        self.cardText = "Goodbye" // <<< This mutation is no good.
        return Text(cardText)
            .onTapGesture {
                self.cardText = "World"
            }
    }
}

在这里,我正在@State视图的主体内修改body变量。问题在于@State变量的突变会导致视图更新,从而又在视图上调用body方法。因此,在调用body的中间,已经发起了对body的另一个调用。这可能会继续下去。

另一方面,@State变量可以onTapGesture块中进行突变,因为它是异步的,不会被调用直到更新完成。

例如,假设我想在用户每次点击文本视图时更改文本。我可能有一个@State变量isFlipped,当点击了文本视图时,手势块中的代码将切换isFlipped变量。 由于它是一个特殊的@State变量,因此更改将驱动视图更新。由于我们不再处于视图更新的中间,因此不会出现“修改状态”在视图更新期间”警告。

struct ContentView: View {
    @State var isFlipped = false
    var body: some View {
        return Text(isFlipped ? "World" : "Hello")
            .onTapGesture {
                self.isFlipped.toggle() // <<< This mutation is ok.
            }
    }
}

对于FlashcardView,您可能想在视图本身之外定义卡,并将其作为初始化参数传递到视图中。

struct Card {
    let kana: String
    let romaji: String
}

let myCard = Card(
    kana: "Hello",
    romaji: "World"
)

struct FlashcardView: View {
    let card: Card
    @State var isFlipped = false
    var body: some View {
        return Text(isFlipped ? card.romaji : card.kana)
            .onTapGesture {
                self.isFlipped.toggle()
            }
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        return FlashcardView(card: myCard)
    }
}
#endif

但是,如果您希望视图在card本身发生变化时发生变化(不是您必须应该将其作为下一步的逻辑操作),那么该代码不足。除了弄清楚突变的发生方式和位置,您还需要导入Combine并重新配置card变量和Card类型本身。这是一个不同的问题。

长话短说:修改手势块中的@State变量。如果要在视图本身之外修改它们,则除了@State注释之外还需要其他内容。 @State仅供本地/私人使用。

(我正在使用Xcode 11 beta 5)

答案 1 :(得分:1)

如果您在不返回 View(因此无法使用 onAppear 或手势)的函数中遇到此问题,另一种解决方法是将更新包装在异步更新:

func updateUIView(_ uiView: ARView, context: Context) {
   if fooVariable { do a thing }
   DispatchQueue.main.async { fooVariable = nil }
}

不过,我无法确定这是否是最佳做法。

答案 2 :(得分:0)

在每次重绘时(如果状态变量发生变化)var body: some View都会重新评估。在您的情况下,这样做会更改另一个状态变量,因为在每次重新评估时都会进行另一个状态变量更改,因此无需缓解就可以在循环中结束。

SwiftUI如何处理此问题既不能保证稳定也不安全。这就是为什么SwiftUI警告您,下次它可能会因此而崩溃。

是由于实现更改,突然触发边缘条件,还是由于从同一变量读取文本时某些异步更改文本而导致运气不好,这给您带来了垃圾字符串/崩溃。

在大多数情况下,您可能会没事的,但这样保证的程度不如平常。