From 68bfd8b404929c05105c22b6342a8ea025c2f0ec Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Wed, 9 Feb 2022 20:38:33 +0100 Subject: [PATCH 1/3] Add support for navigating on an item in a list --- stencils/Navigation.stencil | 71 +++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/stencils/Navigation.stencil b/stencils/Navigation.stencil index def3b5b..be04ada 100644 --- a/stencils/Navigation.stencil +++ b/stencils/Navigation.stencil @@ -98,6 +98,58 @@ struct Navigation: ViewModifier { } } +struct ItemNavigation: ViewModifier { + + enum NavigationType { + case fullscreen + case push + case sheet + } + + @ObservedObject var state: IdentifiableRoutingState + + private let id: ID + private let type: ItemNavigation.NavigationType + private let navigationContent: () -> NavigationContent + private let didSelect: () -> () + + init(state: IdentifiableRoutingState, id: ID, type: ItemNavigation.NavigationType, didSelect: @escaping () -> (), @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 .fullscreen: + content.modifier(FullScreenModifier(isPresented: isActive, builder: { LazyView(navigationContent()) })) + case .sheet: + content.sheet(isPresented: isActive) { LazyView(navigationContent()) } + } + } +} + // Needed for simulating fullscreen in iOS 13 struct FullScreenModifier: ViewModifier { let isPresented: Binding @@ -128,4 +180,23 @@ extension View { ) ) } + + func navigation( + state: IdentifiableRoutingState, + id: ID, + type: ItemNavigation.NavigationType, + didSelect: @escaping () -> (), + @ViewBuilder content: @escaping () -> NavigationContent + ) -> some View { + ModifiedContent( + content: self, + modifier: ItemNavigation( + state: state, + id: id, + type: type, + didSelect: didSelect, + navigationContent: content + ) + ) + } } From 073f922815806fe3a55ea8c8bc4352f2f174df2f Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Wed, 9 Feb 2022 21:03:41 +0100 Subject: [PATCH 2/3] Add identifiable routing state --- stencils/RoutingState.stencil | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/stencils/RoutingState.stencil b/stencils/RoutingState.stencil index 9dc5980..1188d04 100644 --- a/stencils/RoutingState.stencil +++ b/stencils/RoutingState.stencil @@ -65,3 +65,49 @@ class ObjectRoutingState: RoutingState { super.close(animated: animated, force: force) } } + +class IdentifiableRoutingState : RoutingState { + private(set) var id: ID? + + func show(_ id: ID, animated: Bool = false) { + let force: Bool + if self.id != id { + self.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, T.ID == ID { + + private(set) var object: T? + override var id: ID? { + get { + object?.id + } + } + + 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) + } +} From bbd8d366487b915f540203f3d2e7952e07abc3b1 Mon Sep 17 00:00:00 2001 From: Gijs van Veen Date: Mon, 21 Feb 2022 12:02:10 +0100 Subject: [PATCH 3/3] Make IdentifiableRoutingState pass Identifiable Add all navigation types to identifiable routing --- stencils/Navigation.stencil | 91 +++++++++++++++++++++++------------ stencils/RoutingState.stencil | 19 +++----- 2 files changed, 69 insertions(+), 41 deletions(-) diff --git a/stencils/Navigation.stencil b/stencils/Navigation.stencil index be04ada..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,22 +98,22 @@ struct Navigation: ViewModifier { } } -struct ItemNavigation: ViewModifier { - - enum NavigationType { - case fullscreen - case push - case sheet - } +struct ItemNavigation: ViewModifier { @ObservedObject var state: IdentifiableRoutingState - private let id: ID - private let type: ItemNavigation.NavigationType + private let id: ID.ID + private let type: NavigationType private let navigationContent: () -> NavigationContent - private let didSelect: () -> () + private let didSelect: () -> Void - init(state: IdentifiableRoutingState, id: ID, type: ItemNavigation.NavigationType, didSelect: @escaping () -> (), @ViewBuilder navigationContent: @escaping () -> NavigationContent) { + init( + state: IdentifiableRoutingState, + id: ID.ID, + type: NavigationType, + didSelect: @escaping () -> Void, + @ViewBuilder navigationContent: @escaping () -> NavigationContent + ) { self.id = id self.state = state self.type = type @@ -142,10 +142,41 @@ struct ItemNavigation: ViewModifier { 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 %} } } } @@ -168,7 +199,7 @@ struct FullScreenModifier: ViewModifier { extension View { func navigation( state: RoutingState, - type: Navigation.NavigationType, + type: NavigationType, @ViewBuilder content: @escaping () -> NavigationContent ) -> some View { ModifiedContent( @@ -181,11 +212,11 @@ extension View { ) } - func navigation( + func navigation( state: IdentifiableRoutingState, - id: ID, - type: ItemNavigation.NavigationType, - didSelect: @escaping () -> (), + id: ID.ID, + type: NavigationType, + didSelect: @escaping () -> Void, @ViewBuilder content: @escaping () -> NavigationContent ) -> some View { ModifiedContent( diff --git a/stencils/RoutingState.stencil b/stencils/RoutingState.stencil index 1188d04..43e11eb 100644 --- a/stencils/RoutingState.stencil +++ b/stencils/RoutingState.stencil @@ -66,13 +66,12 @@ class ObjectRoutingState: RoutingState { } } -class IdentifiableRoutingState : RoutingState { - private(set) var id: ID? - +class IdentifiableRoutingState: RoutingState, Identifiable { + private(set) var id: ID.ID? func show(_ id: ID, animated: Bool = false) { let force: Bool - if self.id != id { - self.id = id + if self.id != id.id { + self.id = id.id force = true } else { force = false @@ -86,16 +85,14 @@ class IdentifiableRoutingState : RoutingState { } } -class IdentifiableObjectRoutingState: IdentifiableRoutingState where T : Equatable, T : Identifiable, T.ID == ID { +class IdentifiableObjectRoutingState: IdentifiableRoutingState where T: Equatable, T: Identifiable { private(set) var object: T? - override var id: ID? { - get { - object?.id - } + override var id: T.ID? { + return object?.id } - func show(_ object: T, animated: Bool = false) { + override func show(_ object: T, animated: Bool = false) { let force: Bool if self.object != object { self.object = object