最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - How to stack multiple `.fullScreenCover` with using their own navigation - Stack Overflow

programmeradmin4浏览0评论

I have a CoordinatorService that I would like to manage multiple fullscreen covers, each with its own navigation path. My goal is to present them on top of each other, and allow each to navigate independently.

I force the full screen cover to be at position zero, $presentFullScreens[.zero], in order for the first full screen to work for the example.

Below is my minimal example:

import SwiftUI

struct ContentView: View {

  private let coordinator = CoordinatorService.shared

  var body: some View {
    VStack(spacing: 32) {
      Button(action: { coordinator.presentFullScreen(content: .auth) }) {
        Text("Trigger AuthView in full screen")
      }
      .buttonStyle(.borderedProminent)
    }
    .customNavigationStack()
  }
}

#Preview {
  ContentView()
}

enum CoordinatorDestination: Hashable {
  case auth, outcome
}

struct AuthView: View {

  private let coordinator = CoordinatorService.shared

  var body: some View {
    Button(action: { coordinator.presentFullScreen(content: .outcome) }) {
      Text("Authenticate")
    }
    .buttonStyle(.borderedProminent)
    .toolbar {
      ToolbarItem(placement: .cancellationAction) {
        Button(action: { coordinator.dismissFullScreen() }) {
          Text("Dismiss")
        }
      }
    }
    .customNavigationStack(onFullScreen: true)
  }
}


struct OutcomeView: View {

  private let coordinator = CoordinatorService.shared

  var body: some View {
    Text("You have been authenticated!")
      .toolbar {
        ToolbarItem(placement: .cancellationAction) {
          Button(action: { coordinator.dismissFullScreen() }) {
            Text("Dismiss")
          }
        }
      }
      .customNavigationStack(onFullScreen: true)
  }
}

extension View {

  func customNavigationStack(onFullScreen: Bool = false) -> some View {
    modifier(CustomNavigationStackModifier(onFullScreen: onFullScreen))
  }
}

struct CustomNavigationStackModifier: ViewModifier {

  private let coordinator = CoordinatorService.shared

  @State private var rootPath = NavigationPath()
  @State private var fullScreenPaths: [NavigationPath] = [NavigationPath()]
  @State private var presentFullScreens: [Bool] = [false]

  let onFullScreen: Bool

  func body(content: Content) -> some View {

    NavigationStack(path: $rootPath) {
      content
        .onChange(of: coordinator.rootPath) { rootPath = $1 }
        .onChange(of: coordinator.presentFullScreens) { presentFullScreens = $1 }
        .fullScreenCover(
          isPresented: $presentFullScreens[.zero],
          onDismiss: { coordinator.removeLastFullScreenPath() }
        ) {
          NavigationStack(path: $fullScreenPaths[.zero]) {
            fullScreenContent
              .onChange(of: coordinator.fullScreenPaths) { fullScreenPaths = $1 }
          }
        }
    }
  }

  @ViewBuilder var fullScreenContent: some View {
    if let fullScreenView = coordinator.fullScreenViews.last {
      triggerNavigation(to: fullScreenView)
    } else {
      EmptyView()
    }
  }

  @ViewBuilder private func triggerNavigation(
    to destination: CoordinatorDestination
  ) -> some View {

    switch destination {
    case .auth:
      AuthView()
    case .outcome:
      OutcomeView()
    }
  }
}

@MainActor @Observable final class CoordinatorService {
  static let shared = CoordinatorService()

  private(set) var fullScreenPaths = [NavigationPath()]
  private(set) var rootPath = NavigationPath()
  private(set) var presentFullScreens = [false]
  private(set) var fullScreenViews: [CoordinatorDestination] = []

  func navigate(
    to destination: CoordinatorDestination,
    onFullScreen: Bool = false
  ) {
    onFullScreen
    ? fullScreenPaths[fullScreenPaths.count - 1].append(destination)
    : rootPath.append(destination)
  }

  func presentFullScreen(content destination: CoordinatorDestination) {
    if fullScreenViews.count > 1 {
      fullScreenPaths.append(NavigationPath())
      presentFullScreens.append(false)
    }
    fullScreenViews.append(destination)
    navigate(to: destination, onFullScreen: true)
    presentFullScreens[presentFullScreens.count - 1] = true
  }

  func dismissFullScreen() {
    guard !fullScreenViews.isEmpty else { return }
    removeLastFullScreenPath()
    fullScreenViews.removeLast()
    presentFullScreens[presentFullScreens.count - 1] = false
    if presentFullScreens.count > 1 {
      presentFullScreens.removeLast()
    }
  }

  func removeLastFullScreenPath() {
    fullScreenPaths.removeLast()
    guard fullScreenPaths.isEmpty else { return }
    fullScreenPaths.append(NavigationPath())
  }
}

I have tried to put the .fullScreenCover within a ForEach, but it does not stack full screens on top of each others.

 NavigationStack(path: $rootPath) {
   content
     .onChange(of: coordinator.rootPath) { rootPath = $1 }
     .onChange(of: coordinator.presentFullScreens) { presentFullScreens = $1 }
     .overlay {
       ForEach(presentFullScreens.indices, id: \.self) { index in
         EmptyView()
           .fullScreenCover(
             isPresented: $presentFullScreens[index],
             onDismiss: { coordinator.removeLastFullScreenPath() }
           ) {
             NavigationStack(path: $fullScreenPaths[index]) {
               fullScreenContent
                 .onChange(of: coordinator.fullScreenPaths) { fullScreenPaths = $1 }
             }
           }
       }
     }
 }

How can I stack multiple SwiftUI full screen covers on top of each other using a Coordinator pattern?

发布评论

评论列表(0)

  1. 暂无评论