简体   繁体   English

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

[英]SwiftUI: How do I avoid modifying state during view update?

I want to update a text label after it is being pressed, but I am getting this error while my app runs (there is no compile error): [SwiftUI] Modifying state during view update, this will cause undefined behaviour.我想在按下文本标签后更新它,但在我的应用程序运行时出现此错误(没有编译错误):[SwiftUI] 在视图更新期间修改状态,这将导致未定义的行为。

This is my code:这是我的代码:

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

Here I'm modifying a @State variable within the body of the body view.在这里,我正在修改body视图正文中的@State变量。 The problem is that mutations to @State variables cause the view to update, which in turn call the body method on the view.问题是对@State变量的@State会导致视图更新,进而调用视图上的body方法。 So, already in the middle of a call to body , another call to body initiated.因此,已经在调用body ,发起了另一个对body调用。 This could go on and on.这可以继续下去。

On the other hand, a @State variable can be mutated in the onTapGesture block , because it's asynchronous and won't get called until after the update is finished.另一方面, @State变量可以onTapGesture块中改变,因为它是异步的,并且在更新完成后才会被调用。

For example, let's say I want to change the text every time a user taps the text view.例如,假设我想在每次用户点击文本视图时更改文本。 I could have a @State variable isFlipped and when the text view is tapped, the code in the gesture's block toggles the isFlipped variable.我可以有一个@State变量isFlipped并且当点击文本视图时,手势块中的代码会切换isFlipped变量。 Since it's a special @State variable, that change will drive the view to update.由于它是一个特殊的@State变量,因此该更改将驱动视图更新。 Since we're no longer in the middle of a view update, we won't get the "Modifying state during view update" warning.由于我们不再处于视图更新的中间,因此我们不会收到“视图更新期间正在修改状态”警告。

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

For your FlashcardView , you might want to define the card outside of the view itself and pass it into the view as a parameter to initialization.对于您的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

However, if you want the view to change when card itself changes (not that you necessarily should do that as a logical next step), then this code is insufficient.但是,如果您希望card本身更改时更改视图(不是您必须其作为合乎逻辑的下一步),那么此代码是不够的。 You'll need to import Combine and reconfigure the card variable and the Card type itself, in addition to figuring out how and where the mutation going to happen.您需要导入Combine并重新配置card变量和Card类型本身,此外还要弄清楚突变将如何发生以及在何处发生。 And that's a different question.这是一个不同的问题。

Long story short: modify @State variables within gesture blocks.长话短说:修改手势块内的@State变量。 If you want to modify them outside of the view itself, then you need something else besides a @State annotation.如果您想在视图本身之外修改它们,那么除了@State注释之外,您还需要其他东西。 @State is for local/private use only. @State仅供本地/私人使用。

(I'm using Xcode 11 beta 5) (我使用的是 Xcode 11 beta 5)

On every redraw (in case a state variable changes) var body: some View gets reevaluated.在每次重绘时(以防状态变量发生变化) var body: some View被重新评估。 Doing so in your case changes another state variable, which would without mitigation end in a loop since on every reevaluation another state variable change gets made.在您的情况下这样做会更改另一个状态变量,这不会导致循环结束,因为在每次重新评估时都会更改另一个状态变量。

How SwiftUI handles this is neither guaranteed to be stable, nor safe. SwiftUI 处理此问题的方式既不能保证稳定,也不能保证安全。 That is why SwiftUI warns you that next time it may crash due to this.这就是 SwiftUI 警告您下次可能会因此而崩溃的原因。

Be it due to an implementation change, suddenly triggering an edge condition, or bad luck when something async changes text while it is being read from the same variable, giving you a garbage string/crash.无论是由于实现更改,突然触发边缘条件,还是在从同一变量读取文本时异步更改文本时运气不好,给您一个垃圾字符串/崩溃。

In most cases you will probably be fine, but that is less so guaranteed than usual.在大多数情况下,您可能会没事,但这比平常要少。

If you're running into this issue inside a function that isn't returning a View (and therefore can't use onAppear or gestures), another workaround is to wrap the update in an async update:如果您在不返回View (因此无法使用onAppear或手势)的函数中遇到此问题,另一种解决方法是将更新包装在异步更新中:

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

I can't speak to whether this is best practices, however.但是,我无法确定这是否是最佳实践。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 SwiftUI @State 的 UIKit 更新导致“在视图更新期间修改状态” - UIKit update of SwiftUI @State causing "Modifying state during view update" 视图更新期间我要修改什么状态? - What state am I modifying during view update? SwiftUI 计算属性显示“在视图更新期间修改状态,这将导致未定义的行为。” 错误 - SwiftUI Computed Property shows "Modifying state during view update, this will cause undefined behavior." error SwiftUI:如何在函数计算 @State 值的同时进行视图更新? - SwiftUI: How do I make a View update simultaneously while a function is calculating @State value? textFieldDidChangeSelection:在视图更新期间修改 state,这将导致未定义的行为 - textFieldDidChangeSelection: Modifying state during view update, this will cause undefined behavior 从 .onAppear 内部修改状态会导致错误“在视图更新期间修改状态,这将导致未定义的行为” - Modifying state from inside .onAppear causes error 'Modifying state during view update, this will cause undefined behavior' 在 SwiftUI 中,如何在从子视图更新共享 state 数据以避免导航问题时避免重绘父视图? - In SwiftUI, how can I avoid redrawing a parent view while updating shared state data from a child view to avoid Navigation issues? 如何更新嵌入到 UIKit 中的 SwiftUI 视图? - How do I update a SwiftUI View that was embedded into UIKit? 将 function 移出视图:在视图更新期间修改 state,这将导致未定义的行为 - Moving function out of view: Modifying state during view update, this will cause undefined behavior 如何从外部更新 SwiftUI 视图状态(例如 UIViewController) - How to update a SwiftUI view state from outside (UIViewController for example)
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM