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