我认为我对SwiftUI的工作原理有了很好的理解.但有时候还是有一些事情让我困惑,特别是当涉及国家的时候.以下是一些基本代码.

首先,我有一个模型.

struct Item : Equatable {
    let id = UUID()
    let name: String
}

一个ViewModel

@Observable
final class ContentViewModel {
    
    var items = [
        Item(name: "test1"),
        Item(name: "test2"),
        Item(name: "test3")
    ]
    
    func mark(item: Item, wrong: Bool) {
        //deal with right or wrong answer
        items.removeAll {
            $0 == item
        }
    }
    
}

还有一些观点

struct ContentView: View {
    
    let viewModel = ContentViewModel()
    
    var body: some View {
        if let item = viewModel.items.first {
            BackwardableView(item) {
                QuizView(item, fallback: $0)
            }
                .environment(viewModel)
        }
    }
}

struct QuizView : View {
    
    private let item: Item
    @Binding private var fallback: Bool
    @Environment(ContentViewModel.self) private var viewModel
    
    init(_ item: Item, fallback: Binding<Bool>) {
        self.item = item
        self._fallback = fallback
    }
    
    var body: some View {
        VStack {
            Text("Quiz \(item.name)")
            Button("Wrong Answer") {
                fallback = true
            }
            Button("Correct Answer") {
                viewModel.mark(item: item, wrong: false)
            }
        }
        
    }
}

struct FallbackView : View {
    
    private let item: Item
    @Environment(ContentViewModel.self) private var viewModel
    
    init(_ item: Item) {
        self.item = item
    }
    
    var body: some View {
        Text("Fallback \(item.name)")
            .toolbar {
                Button("Next") {
                    viewModel.mark(item: item, wrong: true)
                }
            }
    }
}

struct BackwardableView<Content: View> : View {
    
    private let item: Item
    private let content: (Binding<Bool>) -> Content
    
    @Environment(ContentViewModel.self) private var viewModel
    @State private var backward = false
    
    init(_ item: Item, content: @escaping (Binding<Bool>) -> Content) {
        self.item = item
        self.content = content
    }
    
    var body: some View {
        Group {
            if backward {
                FallbackView(item)
            } else {
                content($backward)
            }
        }
              .id(item.id)
//            .onChange(of: item) { oldValue, newValue in
//                if oldValue == newValue {
//                    return
//                }
//                backward = false
//            }
    }
}

问题出现在最后一个视图中.每当QuizView触发回退时,它应该切换到回退视图并执行一些回退操作.在这个模拟代码中,它简单地要求viewModel删除第一个项目并跳转到下一个项目.然而,BackwardableView似乎保留了下一个项目的"向后状态",这是不合逻辑的.

我试着添加一个显式的id来区分这个视图是全新的.这个技巧通常有效,因为我假设它允许SwiftUI将其识别为一个不同的视图.但在这种情况下它并不有效.添加onChange修饰符解决了这个问题.虽然我理解这种方法是可行的,但我仍然对视图(假定它是一个具有不可变属性(包括项)的 struct )如何明显改变感到困惑.

推荐答案

您从未将backward设置为False,但其初始值除外.

如果你想使用.id(item.id)方法,你应该明白这是通过给一个视图一个新的ID来工作的,因此重置它的所有@State.在这种情况下,您希望backward状态被重置,所以您应该在BackwardableView(包含backward)上放置.id,而不是包装if语句的Group.

struct ContentView: View {
    
    @State var viewModel = ContentViewModel()
    
    var body: some View {
        if let item = viewModel.items.first {
            BackwardableView(item) {
                QuizView(item, fallback: $0)
            }
            .environment(viewModel)
            .id(item.id) // put it here!
        }
    }
}

但在我看来,在FallbackView的基础上再增加Binding会更好,因为这不需要重新创建整个视图.

struct FallbackView : View {
    
    private let item: Item
    @Environment(ContentViewModel.self) private var viewModel
    
    @Binding var backward: Bool
    
    init(_ item: Item, _ backward: Binding<Bool>) {
        self.item = item
        self._backward = backward
    }
    
    var body: some View {
        Text("Fallback \(item.name)")
            .toolbar {
                Button("Next") {
                    viewModel.mark(item: item, wrong: true)
                    backward = false // reset backward here!
                }
            }
    }
}

Swift相关问答推荐

如何使用swift宏生成一个带有关联值的枚举?

如何在visionOS中进行购买?&# 39;购买(选项:)在visionOS中不可用

Swift-Can无法在AudioKit中找出简单的麦克风效果-文件链

在visionOS RealityView中使用.GenerateText时,未显示Reality Composer Promaterial 纹理

仅使用@MainActor注释类的部分时的并发问题

文件命名&NumberForMatter+扩展名&:含义?

NavigationLink和ObservableObject的动画片段

SwiftUI-如何使用剩余时间制作倒计时计时器

如何绑定环境变量ios17

Swift计时器未触发

应该在ViewModel还是ViewController中使用"Task {}"?

除法中如果除以完全因数需要更长时间吗?

在 Swift 5.7 中使用协议作为类型时什么时候需要写 `any`

是否可以使用 .task 修饰符更新基于两个值的视图

为什么 String.contains 在我导入 Foundation 时表现不同?

try 制作一个 Swift 扩展来替换字符串中的多次出现

从 Swift 列表中的行中检索值

带有屏幕参数的 NSWindow 初始化程序在初始化时导致致命错误

如何从 LLDB 调用带有断点的 Swift 函数?

SwiftUI - 呈现工作表后导航栏按钮不可点击