Last active
September 17, 2025 05:12
-
-
Save JadenGeller/a20e1b2cd6434d7755a50d3fe8f6c752 to your computer and use it in GitHub Desktop.
Revisions
-
JadenGeller revised this gist
Jul 13, 2025 . 1 changed file with 8 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,11 @@ /// A view that displays a collection of items in a scrollable list that can "zoom" to focus on a single selected item. /// /// `ZoomNavigator` provides a navigation pattern similar to the iOS lock screen wallpaper picker, /// where items can be viewed together in a scrollable list or individually in full screen. /// The same view instance is maintained during transitions, preserving state and identity. /// /// When no item is selected (`selection` is `nil`), all items are displayed in a scrollable list. /// When an item is selected, the view filters to show only that item, expanded to fill the available space. struct ZoomNavigator<Data: RandomAccessCollection, ID: Hashable, Content: View, Background: View, Modifier: ViewModifier>: View { var data: Data var id: KeyPath<Data.Element, ID> -
JadenGeller created this gist
Jul 13, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,62 @@ struct ZoomNavigator<Data: RandomAccessCollection, ID: Hashable, Content: View, Background: View, Modifier: ViewModifier>: View { var data: Data var id: KeyPath<Data.Element, ID> var selection: ID? var alignment: Alignment = .center var axes: Axis.Set = .vertical // btw: for horizontal, collapsed behavior isn't identical var showsIndicators: Bool = true @ViewBuilder var content: (Data.Element) -> Content @ViewBuilder var background: (Data.Element) -> Background var modifier: (Data.Element) -> Modifier var isZoomedOut: Bool { selection == nil } var body: some View { GeometryReader { geometry in ScrollView(isZoomedOut ? axes : .vertical, showsIndicators: true) { let data = data.filter { item in if let selection { item[keyPath: id] == selection } else { true } } ForEach(data, id: id) { item in content(item) .frame( minWidth: isZoomedOut ? nil : geometry.size.width, minHeight: isZoomedOut ? nil : geometry.size.height, alignment: alignment ) .background( background(item) .frame( minWidth: isZoomedOut ? nil : geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, minHeight: isZoomedOut ? nil : geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom ) .offset( x: isZoomedOut ? 0 : -geometry.safeAreaInsets.leading, y: isZoomedOut ? 0 : -geometry.safeAreaInsets.top ), alignment: .topLeading ) .modifier(modifier(item)) } } .scrollDisabled(true) .frame( minWidth: axes.contains(.vertical) ? geometry.size.width : nil, minHeight: axes.contains(.horizontal) ? geometry.size.height : nil, alignment: alignment ) } } }