【问题标题】:SwiftUI: How do I avoid modifying state during view update?SwiftUI:如何避免在视图更新期间修改状态?
【发布时间】:2019-12-17 22:51:31
【问题描述】:

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

这是我的代码:

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
   }   
}

【问题讨论】:

    标签: swift uilabel swiftui


    【解决方案1】:
    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"
                }
        }
    }
    

    在这里,我正在修改 body 视图主体中的 @State 变量。问题是对@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)

    【讨论】:

    • 感谢您的反馈,这很有意义。我意识到在绘制视图时我必须通过更新卡片文本来进行无限循环。
    • 有谁知道如果出现此错误,Apple 是否会拒绝应用程序。我不是指无限,只是Modifying state during view update, this will cause undefined behavior. 我的情况一切正常,它的行为没有问题,当模式框在父窗口中消失时,键盘会挂起一秒钟
    【解决方案2】:

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

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

    可能是由于实现更改,突然触发边缘条件,或者当某些异步更改文本时从同一个变量中读取时运气不好,给你一个垃圾字符串/崩溃。

    在大多数情况下,您可能会没事,但这比平时少。

    【讨论】:

    • 谢谢,我好像发生了某种无限循环。
    【解决方案3】:

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

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

    但是,我不能说这是否是最佳做法。

    【讨论】:

    • 谢谢。这帮助我解决了我无法解决的问题
    猜你喜欢
    • 2022-11-13
    • 2020-06-12
    • 1970-01-01
    • 1970-01-01
    • 2021-09-17
    • 2023-02-06
    • 2021-08-09
    • 1970-01-01
    • 2020-10-05
    相关资源
    最近更新 更多