【问题标题】:SwiftUI: @State property not updated without weird workaroundSwiftUI:没有奇怪的解决方法,@State 属性不会更新
【发布时间】:2021-05-18 17:30:51
【问题描述】:

@State 属性在另一个视图中更改后未在其原始视图中正确更新,我遇到了奇怪的行为。我正在使用 Xcode 12.3 和 iOS 14。

发生的情况是@State“会话”基于值的项目和@State“流”基于值的项目作为绑定参数发送到另一个视图。当在那里点击一个按钮时,它会更改它们的值,并且原始视图中的 fullScreenCover 调用应该从 switch 语句中获得正确的视图以显示在流中的下一个视图。但是,除非我包含一个 onChange 修饰符来查找两个 @State 属性中的任何一个中的更改,否则该 switch 语句中的“会话”项是 nil。 onChange 调用不必包含任何代码即可产生此效果。

我对 SwiftUI 还是比较陌生(尽管在 iOS 和 Mac 开发方面经验丰富)。但这让我很困惑。我不明白为什么它没有按预期工作,也不明白为什么添加一个空的 onChange 处理程序可以让它工作。

如果您想亲自体验一下,下面是组装一个简单演示项目的代码:

// the model types

struct ObservationSession: Codable {
    public let id: UUID
    public var name: String
    
    public init(name: String) {
        self.name = name
        self.id = UUID()
    }

}

struct SessionListModals {
    enum Flow: Identifiable {
        case configuration
        case observation
        case newSession
        
        var id: Flow { self }
    }
}

// ContentView

struct ContentView: View {
    @State private var mutableSession: ObservationSession?
    @State private var flow: SessionListModals.Flow?
    var body: some View {
        VStack {
            Button("New Session", action: {
                mutableSession = ObservationSession(name: "")
                flow = .newSession
            })
                .padding()
        }
        .fullScreenCover(item: $flow) {
                viewForFlow($0)
        }
        
        // Uncomment either of these 2 onChange blocks to see successful execution of this flow
        // Why does that make a difference?
        
//        .onChange(of: mutableSession?.name, perform: { value in
//            //
//        })
        
//        .onChange(of: flow, perform: { value in
//            //
//        })
    }
    
    @ViewBuilder private func viewForFlow(_ flow: SessionListModals.Flow) -> some View {
        switch flow {
        case .newSession:
            // MARK: - Show New Session View
            NavigationView {
                NewSessionView(session: $mutableSession, flow: $flow)
                    .navigationTitle("Create a session")
                    .navigationBarItems(leading: Button("Cancel", action: {
                        self.flow = nil
                    }))
            }
        case .observation:
            // MARK: - Show RecordingView
            NavigationView {
                let name = mutableSession?.name ?? "Unnamed session"
                RecordingView(sessionName: name)
                    .navigationBarItems(leading: Button("Close", action: {
                        self.flow = nil
                    }))
            }
        default:
            NavigationView {
                EmptyView()
                    .navigationBarItems(leading: Button("Close", action: {
                        self.flow = nil
                    }))
            }
        }
    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

// NewSessionView

struct NewSessionView: View {
    @Binding var session: ObservationSession?
    @Binding var flow: SessionListModals.Flow?
    var body: some View {
        VStack {
            Text("Tap button to create a new session")
            Button("New Session", action: {
                createNewSession()
            })
            .padding()
        }
    }
    
    private func createNewSession() {
        let newSession = ObservationSession(name: "Successfully Created A New Session")
        session = newSession
        flow = .observation
    }
}

struct NewSessionView_Previews: PreviewProvider {
    static let newSession = ObservationSession(name: "Preview")
    static let flow: SessionListModals.Flow = .newSession
    static var previews: some View {
        NewSessionView(session: .constant(newSession), flow: .constant(flow))
    }
}

// RecordingView

struct RecordingView: View {
    var sessionName: String
    var body: some View {
        Text(sessionName)
    }
}

struct RecordingView_Previews: PreviewProvider {
    static var previews: some View {
        RecordingView(sessionName: "Preview")
    }
}


【问题讨论】:

    标签: swiftui swiftui-state


    【解决方案1】:
    class ObservationSession: //Codable, //implement Codable manually
        ObservableObject {
        public let id: UUID
        //This allows you to observe the individual variable
        @Published public var name: String
        
        public init(name: String) {
            self.name = name
            self.id = UUID()
        }
        
    }
    
    struct SessionListModals {
        enum Flow: Identifiable {
            case configuration
            case observation
            case newSession
            
            var id: Flow { self }
        }
    }
    
    // ContentView
    class ContentViewModel: ObservableObject {
        @Published var mutableSession: ObservationSession?
        
    }
    struct ContentView: View {
        //State stores the entire object and observes it as a whole it does not individually observe its variables that is why .onChange works
        @StateObject var vm: ContentView3Model = ContentView3Model()
        @State private var flow: SessionListModals.Flow?
        var body: some View {
            VStack {
                Button("New Session", action: {
                    //Since you want to change it programatically you have to put them in another object
                    vm.mutableSession = ObservationSession(name: "")
                    flow = .newSession
                })
                .padding()
            }
            .fullScreenCover(item: $flow) {
                viewForFlow($0)
            }
        }
        
        @ViewBuilder private func viewForFlow(_ flow: SessionListModals.Flow) -> some View {
            switch flow {
            case .newSession:
                // MARK: - Show New Session View
                NavigationView {
                    NewSessionView(session: $vm.mutableSession, flow: $flow)
                        .navigationTitle("Create a session")
                        .navigationBarItems(leading: Button("Cancel", action: {
                            self.flow = nil
                        }))
                }
            case .observation:
                // MARK: - Show RecordingView
                NavigationView {
                    let name = vm.mutableSession?.name ?? "Unnamed session"
                    RecordingView(sessionName: name)
                        .navigationBarItems(leading: Button("Close", action: {
                            self.flow = nil
                        }))
                }
            default:
                NavigationView {
                    EmptyView()
                        .navigationBarItems(leading: Button("Close", action: {
                            self.flow = nil
                        }))
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2021-01-06
      • 1970-01-01
      • 2022-01-07
      • 2021-10-19
      • 1970-01-01
      • 1970-01-01
      • 2020-04-10
      • 2013-11-24
      相关资源
      最近更新 更多