SwiftUI: свойство @State не обновляется без странного обходного пути

Я испытываю странное поведение со свойством @State, которое не обновляется должным образом в исходном представлении после изменения в другом представлении. Я использую Xcode 12.3 и iOS 14.

Что происходит, так это то, что элемент на основе значения @State "сеанс" и элемент на основе значения @State "поток" отправляются как связанные параметры в другое представление. Когда там нажимается кнопка, она меняет свои значения, а вызов fullScreenCover в исходном представлении должен получить правильное представление, которое будет отображаться следующим в потоке из оператора switch. Но элемент «сеанс» в этом операторе переключения равен нулю, если я не включу модификатор onChange, который ищет изменения в любом из двух свойств @State. Для этого в вызове 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")
    }
}


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
                    }))
            }
        }
    }
}
Другие вопросы по тегам