我想在按下文本标签后更新文本标签,但是我的应用程序运行时出现此错误(没有编译错误):[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
}
}
答案 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警告您,下次它可能会因此而崩溃。
是由于实现更改,突然触发边缘条件,还是由于从同一变量读取文本时某些异步更改文本而导致运气不好,这给您带来了垃圾字符串/崩溃。
在大多数情况下,您可能会没事的,但这样保证的程度不如平常。