Explorar el Código

wizard: use NavigationStack for iOS 16 + visionOS 1

osy hace 2 años
padre
commit
44cdd8bfba
Se han modificado 2 ficheros con 114 adiciones y 51 borrados
  1. 7 5
      Platform/Shared/VMWizardState.swift
  2. 107 46
      Platform/iOS/VMWizardView.swift

+ 7 - 5
Platform/Shared/VMWizardState.swift

@@ -54,7 +54,11 @@ enum VMWizardOS: String, Identifiable {
     
     @Published var slide: AnyTransition = .identity
     @Published var currentPage: VMWizardPage = .start
-    private var pageHistory = [VMWizardPage]()
+    @Published var pageHistory = [VMWizardPage]() {
+        didSet {
+            currentPage = pageHistory.last ?? .start
+        }
+    }
     @Published var nextPageBinding: Binding<VMWizardPage?> = .constant(nil)
     @Published var alertMessage: AlertMessage?
     @Published var isBusy: Bool = false
@@ -245,18 +249,16 @@ enum VMWizardOS: String, Identifiable {
         }
         slide = slideIn
         withAnimation {
-            pageHistory.append(currentPage)
-            currentPage = nextPage
+            pageHistory.append(nextPage)
             nextPageBinding.wrappedValue = nextPage
             nextPageBinding = .constant(nil)
         }
     }
     
     func back() {
-        let previousPage = pageHistory.popLast() ?? .start
         slide = slideOut
         withAnimation {
-            currentPage = previousPage
+            _ = pageHistory.popLast()
         }
     }
     

+ 107 - 46
Platform/iOS/VMWizardView.swift

@@ -21,61 +21,31 @@ struct VMWizardView: View {
     @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
     
     var body: some View {
-        NavigationView {
-            WizardWrapper(page: .start, wizardState: wizardState) {
+        if #available(iOS 16, visionOS 1.0, *) {
+            WizardNavigationView(wizardState: wizardState) {
                 presentationMode.wrappedValue.dismiss()
             }
-        }.navigationViewStyle(.stack)
-        .alert(item: $wizardState.alertMessage) { msg in
-            Alert(title: Text(msg.message))
+        } else {
+            NavigationView {
+                WizardWrapper(page: .start, wizardState: wizardState) {
+                    presentationMode.wrappedValue.dismiss()
+                }
+            }
+            .navigationViewStyle(.stack)
+            .alert(item: $wizardState.alertMessage) { msg in
+                Alert(title: Text(msg.message))
+            }
         }
     }
 }
 
-fileprivate struct WizardWrapper: View {
-    let page: VMWizardPage
+fileprivate struct WizardToolbar: ViewModifier {
     @ObservedObject var wizardState: VMWizardState
-    @State private var nextPage: VMWizardPage?
     let onDismiss: () -> Void
     @EnvironmentObject private var data: UTMData
-    
-    var body: some View {
-        VStack {
-            switch page {
-            case .start:
-                VMWizardStartView(wizardState: wizardState)
-            case .operatingSystem:
-                VMWizardOSView(wizardState: wizardState)
-            case .macOSBoot:
-                EmptyView()
-            case .linuxBoot:
-                VMWizardOSLinuxView(wizardState: wizardState)
-            case .windowsBoot:
-                VMWizardOSWindowsView(wizardState: wizardState)
-            case .otherBoot:
-                VMWizardOSOtherView(wizardState: wizardState)
-            case .hardware:
-                VMWizardHardwareView(wizardState: wizardState)
-            case .drives:
-                VMWizardDrivesView(wizardState: wizardState)
-            case .sharing:
-                VMWizardSharingView(wizardState: wizardState)
-            case .summary:
-                VMWizardSummaryView(wizardState: wizardState)
-            }
-            NavigationLink(destination: WizardWrapper(page: .start, wizardState: wizardState, onDismiss: onDismiss), tag: .start, selection: $nextPage) {}
-            NavigationLink(destination: WizardWrapper(page: .operatingSystem, wizardState: wizardState, onDismiss: onDismiss), tag: .operatingSystem, selection: $nextPage) {}
-            NavigationLink(destination: WizardWrapper(page: .linuxBoot, wizardState: wizardState, onDismiss: onDismiss), tag: .linuxBoot, selection: $nextPage) {}
-            NavigationLink(destination: WizardWrapper(page: .windowsBoot, wizardState: wizardState, onDismiss: onDismiss), tag: .windowsBoot, selection: $nextPage) {}
-            NavigationLink(destination: WizardWrapper(page: .otherBoot, wizardState: wizardState, onDismiss: onDismiss), tag: .otherBoot, selection: $nextPage) {}
-            NavigationLink(destination: WizardWrapper(page: .hardware, wizardState: wizardState, onDismiss: onDismiss), tag: .hardware, selection: $nextPage) {}
-            NavigationLink(destination: WizardWrapper(page: .drives, wizardState: wizardState, onDismiss: onDismiss), tag: .drives, selection: $nextPage) {}
-            NavigationLink(destination: WizardWrapper(page: .sharing, wizardState: wizardState, onDismiss: onDismiss), tag: .sharing, selection: $nextPage) {}
-            NavigationLink(destination: WizardWrapper(page: .summary, wizardState: wizardState, onDismiss: onDismiss), tag: .summary, selection: $nextPage) {}
-        }
-        .listStyle(.insetGrouped) // needed for iOS 14
-        .textFieldStyle(.roundedBorder)
-        .toolbar {
+
+    func body(content: Content) -> some View {
+        content.toolbar {
             ToolbarItem(placement: .navigationBarLeading) {
                 if wizardState.currentPage == .start {
                     Button("Cancel") {
@@ -112,6 +82,34 @@ fileprivate struct WizardWrapper: View {
                 }
             }
         }
+    }
+}
+
+@available(iOS, deprecated: 17, message: "Use WizardViewWrapper")
+@available(visionOS, deprecated: 1, message: "Use WizardViewWrapper")
+fileprivate struct WizardWrapper: View {
+    let page: VMWizardPage
+    @ObservedObject var wizardState: VMWizardState
+    @State private var nextPage: VMWizardPage?
+    let onDismiss: () -> Void
+    @EnvironmentObject private var data: UTMData
+    
+    var body: some View {
+        VStack {
+            WizardViewWrapper(page: page, wizardState: wizardState)
+            NavigationLink(destination: WizardWrapper(page: .start, wizardState: wizardState, onDismiss: onDismiss), tag: .start, selection: $nextPage) {}
+            NavigationLink(destination: WizardWrapper(page: .operatingSystem, wizardState: wizardState, onDismiss: onDismiss), tag: .operatingSystem, selection: $nextPage) {}
+            NavigationLink(destination: WizardWrapper(page: .linuxBoot, wizardState: wizardState, onDismiss: onDismiss), tag: .linuxBoot, selection: $nextPage) {}
+            NavigationLink(destination: WizardWrapper(page: .windowsBoot, wizardState: wizardState, onDismiss: onDismiss), tag: .windowsBoot, selection: $nextPage) {}
+            NavigationLink(destination: WizardWrapper(page: .otherBoot, wizardState: wizardState, onDismiss: onDismiss), tag: .otherBoot, selection: $nextPage) {}
+            NavigationLink(destination: WizardWrapper(page: .hardware, wizardState: wizardState, onDismiss: onDismiss), tag: .hardware, selection: $nextPage) {}
+            NavigationLink(destination: WizardWrapper(page: .drives, wizardState: wizardState, onDismiss: onDismiss), tag: .drives, selection: $nextPage) {}
+            NavigationLink(destination: WizardWrapper(page: .sharing, wizardState: wizardState, onDismiss: onDismiss), tag: .sharing, selection: $nextPage) {}
+            NavigationLink(destination: WizardWrapper(page: .summary, wizardState: wizardState, onDismiss: onDismiss), tag: .summary, selection: $nextPage) {}
+        }
+        .listStyle(.insetGrouped) // needed for iOS 14
+        .textFieldStyle(.roundedBorder)
+        .modifier(WizardToolbar(wizardState: wizardState, onDismiss: onDismiss))
         .onChange(of: nextPage) { newPage in
             if newPage == nil {
                 wizardState.currentPage = page
@@ -126,6 +124,69 @@ fileprivate struct WizardWrapper: View {
     }
 }
 
+@available(iOS 16, visionOS 1.0, *)
+fileprivate struct WizardNavigationView: View {
+    @StateObject var wizardState = VMWizardState()
+    let onDismiss: () -> Void
+    @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
+    @State private var navigationPath: NavigationPath = .init()
+    @State private var previousPage: VMWizardPage?
+    @State private var isAlertShown: Bool = false
+    
+    var body: some View {
+        NavigationStack(path: $wizardState.pageHistory) {
+            WizardViewWrapper(page: .start, wizardState: wizardState)
+                .modifier(WizardToolbar(wizardState: wizardState, onDismiss: onDismiss))
+                .navigationDestination(for: VMWizardPage.self) { page in
+                    WizardViewWrapper(page: page, wizardState: wizardState)
+                        .modifier(WizardToolbar(wizardState: wizardState, onDismiss: onDismiss))
+                }
+                .textFieldStyle(.roundedBorder)
+                .disabled(wizardState.isBusy)
+        }
+        .alert("Error", isPresented: $isAlertShown) {
+            Button("OK", role: .cancel) {
+                wizardState.alertMessage = nil
+            }
+        } message: {
+            Text(wizardState.alertMessage?.message ?? "")
+        }
+        .onChange(of: wizardState.alertMessage?.message) { newValue in
+            isAlertShown = newValue != nil
+        }
+    }
+}
+
+fileprivate struct WizardViewWrapper: View {
+    let page: VMWizardPage
+    @ObservedObject var wizardState: VMWizardState
+
+    var body: some View {
+        switch page {
+        case .start:
+            VMWizardStartView(wizardState: wizardState)
+        case .operatingSystem:
+            VMWizardOSView(wizardState: wizardState)
+        case .macOSBoot:
+            EmptyView()
+        case .linuxBoot:
+            VMWizardOSLinuxView(wizardState: wizardState)
+        case .windowsBoot:
+            VMWizardOSWindowsView(wizardState: wizardState)
+        case .otherBoot:
+            VMWizardOSOtherView(wizardState: wizardState)
+        case .hardware:
+            VMWizardHardwareView(wizardState: wizardState)
+        case .drives:
+            VMWizardDrivesView(wizardState: wizardState)
+        case .sharing:
+            VMWizardSharingView(wizardState: wizardState)
+        case .summary:
+            VMWizardSummaryView(wizardState: wizardState)
+        }
+    }
+}
+
 struct VMWizardView_Previews: PreviewProvider {
     static var previews: some View {
         VMWizardView()