diff --git a/InfiniteNavigationDemo/InfiniteNavigationDemo/Navigation/MyNavigation.swift b/InfiniteNavigationDemo/InfiniteNavigationDemo/Navigation/MyNavigation.swift index 89cd361..995d273 100644 --- a/InfiniteNavigationDemo/InfiniteNavigationDemo/Navigation/MyNavigation.swift +++ b/InfiniteNavigationDemo/InfiniteNavigationDemo/Navigation/MyNavigation.swift @@ -10,8 +10,8 @@ final class MyNavigation: HomeNavigation, SheetNavigation, DetailNavigation { // MARK: - HomeNavigation - func showSheet() { - navigateTo.send(.show(.sheet(.sheet))) + func showSheet(style: SheetPresentationStlye) { + navigateTo.send(.show(.sheet(.sheet, style: style))) } func showDetail() { diff --git a/InfiniteNavigationDemo/InfiniteNavigationDemo/Views/HomeView.swift b/InfiniteNavigationDemo/InfiniteNavigationDemo/Views/HomeView.swift index e288c67..d945482 100644 --- a/InfiniteNavigationDemo/InfiniteNavigationDemo/Views/HomeView.swift +++ b/InfiniteNavigationDemo/InfiniteNavigationDemo/Views/HomeView.swift @@ -1,7 +1,8 @@ import SwiftUI +import InfiniteNavigation protocol HomeNavigation { - func showSheet() + func showSheet(style: SheetPresentationStlye) func showDetail() } diff --git a/InfiniteNavigationDemo/InfiniteNavigationDemo/Views/NavFooterView.swift b/InfiniteNavigationDemo/InfiniteNavigationDemo/Views/NavFooterView.swift index 6b8a71a..88b1831 100644 --- a/InfiniteNavigationDemo/InfiniteNavigationDemo/Views/NavFooterView.swift +++ b/InfiniteNavigationDemo/InfiniteNavigationDemo/Views/NavFooterView.swift @@ -1,20 +1,27 @@ import SwiftUI +import InfiniteNavigation struct NavFooterView: View { typealias Completion = () -> Void - let showSheet: Completion + let showSheet: (SheetPresentationStlye) -> Void let showDetail: Completion var popDetails: Completion? var body: some View { - HStack { - Button("Show Detail", action: showDetail) - if let popDetails = popDetails { - Button("Pop Details", action: popDetails) + VStack { + HStack { + Button("Show Detail", action: showDetail) + if let popDetails = popDetails { + Button("Pop Details", action: popDetails) + } + } + + HStack { + Button("Show Full Sheet") { showSheet(.fullScreen) } + Button("Show Modal Sheet") { showSheet(.modal) } } - Button("Show Sheet", action: showSheet) } .buttonStyle(.bordered) } @@ -23,8 +30,8 @@ struct NavFooterView: View { #if DEBUG struct NavFooterView_Previews: PreviewProvider { static var previews: some View { - NavFooterView(showSheet: {}, showDetail: {}) - NavFooterView(showSheet: {}, showDetail: {}, popDetails: {}) + NavFooterView(showSheet: { _ in }, showDetail: {}) + NavFooterView(showSheet: { _ in }, showDetail: {}, popDetails: {}) } } #endif diff --git a/Sources/InfiniteNavigation/InfiniteNavContainer.swift b/Sources/InfiniteNavigation/InfiniteNavContainer.swift index 95b8595..40b924b 100644 --- a/Sources/InfiniteNavigation/InfiniteNavContainer.swift +++ b/Sources/InfiniteNavigation/InfiniteNavContainer.swift @@ -5,9 +5,16 @@ import Combine internal struct Sheet: Identifiable { let id = UUID().uuidString var path = NavigationPath() + let style: SheetPresentationStlye let source: () -> AnyView } +@available(iOS 16.0, *) +internal struct RootContainer { + var path: NavigationPath + let source: () -> T +} + public typealias Environments = [any ObservableObject] @available(iOS 16.0, *) @@ -20,7 +27,8 @@ public struct InfiniteNavContainer: View { private let viewBuilder: NavDestinationBuilder private let environments: Environments - @State private var stack: [Sheet] + @State private var stack = [Sheet]() + @State private var root: RootContainer init( initialStack: [Destination] = [], @@ -29,9 +37,7 @@ public struct InfiniteNavContainer: View { viewBuilder: @escaping NavDestinationBuilder, root: @escaping () -> Root ) { - _stack = .init(initialValue: [ - .init(path: NavigationPath(initialStack), source: { root().toAnyView() }) - ]) + _root = .init(initialValue: .init(path: NavigationPath(initialStack), source: root)) self.navAction = navAction self.viewBuilder = viewBuilder @@ -39,12 +45,12 @@ public struct InfiniteNavContainer: View { } public var body: some View { - root + render(root: $root) .onReceive(navAction.receiveOnMain()) { switch $0 { case .show(let action): switch action { - case .sheet(let destination): stack.append(.init { viewBuilder(destination) }) + case .sheet(let destination, let style): stack.append(.init(style: style) { viewBuilder(destination) }) case .detail(let destination): mutateCurrentPath { $0.append(destination) } } case .setStack(let destinations): mutateCurrentPath { $0.append(contentsOf: destinations) } @@ -59,28 +65,47 @@ public struct InfiniteNavContainer: View { @available(iOS 16.0, *) extension InfiniteNavContainer { - private var root: some View { - guard let root = $stack.first else { - fatalError("Root view unexpectedly missing.") - } - return render(sheet: root) + private func render(root: Binding>) -> some View { + render(source: root.wrappedValue.source, path: root.path, id: nil) } private func render(sheet: Binding) -> AnyView { - NavigationStack(path: sheet.path) { - wrap(sheet.wrappedValue.source()) + render(source: sheet.wrappedValue.source, path: sheet.path, id: sheet.wrappedValue.id) + .toAnyView() + } + + private func render(source: () -> some View, path: Binding, id: String?) -> some View { + let nextSheetBinding = Binding( + get: { nextSheet(after: id) }, + set: { + if $0 == nil && stack.last?.id == nextSheet(after: id)?.id { + dismiss() + } + } + ) + + let updatableSheet: (Sheet) -> Binding = { sheet in + .init( + get: { sheet }, + set: { if $0.id == sheet.id { update(sheet: $0) } } + ) + } + + return NavigationStack(path: path) { + let content = wrap(source()) .navigationDestination(for: Destination.self) { wrap(viewBuilder($0)) } - .fullScreenCover(item: Binding( - get: { next(after: sheet.wrappedValue) }, - set: { if $0 == nil && stack.last?.id == next(after: sheet.wrappedValue)?.id { dismiss() } } - )) { sheet in - render(sheet: .init( - get: { sheet }, - set: { if $0.id == sheet.id { update(sheet: $0) } } - )) + + if let style = nextSheetBinding.wrappedValue?.style { + switch style { + case .fullScreen: + content.fullScreenCover(item: nextSheetBinding) { render(sheet: updatableSheet($0)) }.id(id) + case .modal: + content.sheet(item: nextSheetBinding) { render(sheet: updatableSheet($0)) }.id(id) } + } else { + content.id(id) + } } - .toAnyView() } private func wrap(_ view: some View) -> some View { @@ -90,7 +115,11 @@ extension InfiniteNavContainer { } private func mutateCurrentPath(_ mutate: (inout NavigationPath) -> Void) { - mutate(&stack[stack.count - 1].path) + if stack.isEmpty { + mutate(&root.path) + } else { + mutate(&stack[stack.count - 1].path) + } } private func update(sheet: Sheet) { @@ -100,8 +129,9 @@ extension InfiniteNavContainer { stack[index] = sheet } - private func next(after sheet: Sheet) -> Sheet? { - guard let index = stack.firstIndex(where: { $0.id == sheet.id }) else { return nil } + private func nextSheet(after id: String? = nil) -> Sheet? { + guard let id = id else { return stack.first } + guard let index = stack.firstIndex(where: { $0.id == id }) else { return nil } return stack[safe: index + 1] } diff --git a/Sources/InfiniteNavigation/LegacyInfiniteNavContainer.swift b/Sources/InfiniteNavigation/LegacyInfiniteNavContainer.swift index 91f2bee..eff7d6b 100644 --- a/Sources/InfiniteNavigation/LegacyInfiniteNavContainer.swift +++ b/Sources/InfiniteNavigation/LegacyInfiniteNavContainer.swift @@ -80,11 +80,16 @@ public struct LegacyInfiniteNavContainer: UIViewContro case .detail(let detail): let vc = self.wrap(self.viewBuilder(detail)) self.navigationController?.pushViewController(vc, animated: true) - case .sheet(let sheet): + case .sheet(let sheet, let style): let vc = self.wrap(self.viewBuilder(sheet)) let navVc = UINavigationController(rootViewController: vc) navVc.navigationBar.isHidden = true - navVc.modalPresentationStyle = .fullScreen + switch style { + case .fullScreen: + navVc.modalPresentationStyle = .fullScreen + case .modal: + navVc.modalPresentationStyle = .popover + } self.navigationController?.present(navVc, animated: true) } case .setStack(let stack): diff --git a/Sources/InfiniteNavigation/NavDestination.swift b/Sources/InfiniteNavigation/NavDestination.swift index 054d5b2..1b50630 100644 --- a/Sources/InfiniteNavigation/NavDestination.swift +++ b/Sources/InfiniteNavigation/NavDestination.swift @@ -1,6 +1,11 @@ import Foundation +public enum SheetPresentationStlye { + case fullScreen + case modal +} + public enum NavDestination { case detail(T) - case sheet(T) + case sheet(T, style: SheetPresentationStlye) }