diff --git a/stencils/Navigation.stencil b/stencils/Navigation.stencil index def3b5b..5900aef 100644 --- a/stencils/Navigation.stencil +++ b/stencils/Navigation.stencil @@ -1,19 +1,19 @@ import SwiftUI {% if argument.includePartialSheet %}import PartialSheet{% endif %} -struct Navigation: ViewModifier { - - enum NavigationType { - case fullscreen - case hud - case push - case replace - case sheet - case cover +enum NavigationType { + case fullscreen + case hud(blurRadius: CGFloat = 3, opacity: Double = 0.5) + case push + case replace + case sheet + case cover {% if argument.includePartialSheet %} - case partialSheet(style: PartialSheetStyle) + case partialSheet(style: PartialSheetStyle) {% endif %} - } +} + +struct Navigation: ViewModifier { @ObservedObject var state: RoutingState @@ -39,11 +39,11 @@ struct Navigation: ViewModifier { } content } - case .hud: + case let .hud(blurRadius, opacity): Group { content - .blur(radius: state.isPresented ? 3 : 0) - .opacity(state.isPresented ? 0.5 : 1.0) + .blur(radius: state.isPresented ? blurRadius : 0) + .opacity(state.isPresented ? opacity : 1.0) .disabled(state.isPresented) if state.isPresented { LazyView(navigationContent()) @@ -98,6 +98,89 @@ struct Navigation: ViewModifier { } } +struct ItemNavigation: ViewModifier { + + @ObservedObject var state: IdentifiableRoutingState + + private let id: ID.ID + private let type: NavigationType + private let navigationContent: () -> NavigationContent + private let didSelect: () -> Void + + init( + state: IdentifiableRoutingState, + id: ID.ID, + type: NavigationType, + didSelect: @escaping () -> Void, + @ViewBuilder navigationContent: @escaping () -> NavigationContent + ) { + self.id = id + self.state = state + self.type = type + self.navigationContent = navigationContent + self.didSelect = didSelect + } + + @ViewBuilder + func body(content: Content) -> some View { + let isActive = Binding { + state.isPresented && state.id == self.id + } set: { isActive in + if isActive { + self.didSelect() + } else { + state.close() + } + } + switch type { + case .push: + VStack { + NavigationLink( + destination: LazyView(navigationContent()), + isActive: isActive + ) { + content + } + } + case let .hud(blurRadius, opacity): + Group { + content + .blur(radius: isActive.wrappedValue ? blurRadius : 0) + .opacity(isActive.wrappedValue ? opacity : 1.0) + .disabled(isActive.wrappedValue) + if isActive.wrappedValue { + LazyView(navigationContent()) + } + } + case .fullscreen: + content.modifier(FullScreenModifier(isPresented: isActive, builder: { LazyView(navigationContent()) })) + case .sheet: + content.sheet(isPresented: isActive) { LazyView(navigationContent()) } + case .replace: + if isActive.wrappedValue { + LazyView(navigationContent()) + } else { + content + } + case .cover: + Group { + content + if isActive.wrappedValue { + LazyView(navigationContent()) + } + } + {% if argument.includePartialSheet %} + case .partialSheet(let style): + content + .addPartialSheet(style: style) + .partialSheet(isPresented: isActive) { + LazyView(navigationContent()) + } + {% endif %} + } + } +} + // Needed for simulating fullscreen in iOS 13 struct FullScreenModifier: ViewModifier { let isPresented: Binding @@ -116,7 +199,7 @@ struct FullScreenModifier: ViewModifier { extension View { func navigation( state: RoutingState, - type: Navigation.NavigationType, + type: NavigationType, @ViewBuilder content: @escaping () -> NavigationContent ) -> some View { ModifiedContent( @@ -128,4 +211,23 @@ extension View { ) ) } + + func navigation( + state: IdentifiableRoutingState, + id: ID.ID, + type: NavigationType, + didSelect: @escaping () -> Void, + @ViewBuilder content: @escaping () -> NavigationContent + ) -> some View { + ModifiedContent( + content: self, + modifier: ItemNavigation( + state: state, + id: id, + type: type, + didSelect: didSelect, + navigationContent: content + ) + ) + } } diff --git a/stencils/RoutingState.stencil b/stencils/RoutingState.stencil index 9dc5980..43e11eb 100644 --- a/stencils/RoutingState.stencil +++ b/stencils/RoutingState.stencil @@ -65,3 +65,46 @@ class ObjectRoutingState: RoutingState { super.close(animated: animated, force: force) } } + +class IdentifiableRoutingState: RoutingState, Identifiable { + private(set) var id: ID.ID? + func show(_ id: ID, animated: Bool = false) { + let force: Bool + if self.id != id.id { + self.id = id.id + force = true + } else { + force = false + } + super.show(animated: animated, force: force) + } + + override func close(animated: Bool = false, force: Bool = false) { + id = nil + super.close(animated: animated, force: force) + } +} + +class IdentifiableObjectRoutingState: IdentifiableRoutingState where T: Equatable, T: Identifiable { + + private(set) var object: T? + override var id: T.ID? { + return object?.id + } + + override func show(_ object: T, animated: Bool = false) { + let force: Bool + if self.object != object { + self.object = object + force = true + } else { + force = false + } + super.show(animated: animated, force: force) + } + + override func close(animated: Bool = false, force: Bool = false) { + object = nil + super.close(animated: animated, force: force) + } +}