|
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 |
|
) |
|
} |
|
} |
|
} |
example usage