Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sheet presentation styles #7

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import SwiftUI
import InfiniteNavigation

protocol HomeNavigation {
func showSheet()
func showSheet(style: SheetPresentationStlye)
func showDetail()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
Expand All @@ -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
80 changes: 55 additions & 25 deletions Sources/InfiniteNavigation/InfiniteNavContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: View> {
var path: NavigationPath
let source: () -> T
}

public typealias Environments = [any ObservableObject]

@available(iOS 16.0, *)
Expand All @@ -20,7 +27,8 @@ public struct InfiniteNavContainer<Destination: Hashable, Root: View>: View {
private let viewBuilder: NavDestinationBuilder
private let environments: Environments

@State private var stack: [Sheet]
@State private var stack = [Sheet]()
@State private var root: RootContainer<Root>

init(
initialStack: [Destination] = [],
Expand All @@ -29,22 +37,20 @@ public struct InfiniteNavContainer<Destination: Hashable, Root: View>: 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
self.environments = environments
}

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) }
Expand All @@ -59,28 +65,47 @@ public struct InfiniteNavContainer<Destination: Hashable, Root: View>: 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<RootContainer<Root>>) -> some View {
render(source: root.wrappedValue.source, path: root.path, id: nil)
}

private func render(sheet: Binding<Sheet>) -> 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<NavigationPath>, id: String?) -> some View {
let nextSheetBinding = Binding<Sheet?>(
get: { nextSheet(after: id) },
set: {
if $0 == nil && stack.last?.id == nextSheet(after: id)?.id {
dismiss()
}
}
)

let updatableSheet: (Sheet) -> Binding<Sheet> = { 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<Sheet?>(
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 {
Expand All @@ -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) {
Expand All @@ -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]
}

Expand Down
9 changes: 7 additions & 2 deletions Sources/InfiniteNavigation/LegacyInfiniteNavContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,16 @@ public struct LegacyInfiniteNavContainer<Root: SwiftUI.View, View>: 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):
Expand Down
7 changes: 6 additions & 1 deletion Sources/InfiniteNavigation/NavDestination.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import Foundation

public enum SheetPresentationStlye {
case fullScreen
case modal
}

public enum NavDestination<T> {
case detail(T)
case sheet(T)
case sheet(T, style: SheetPresentationStlye)
}