I'm trying to make something like this:
Essentially:
I've tried using a ScrollView but I have no idea of how to implement 2 and 4. I guess the idea is quite similar to a Picker?
I've been stuck on this for a while. Any suggestion would be greatly appreciated. Thanks in advance!
You could detect the location of items to select the centred one.
// Data model
struct Item: Identifiable {
let id = UUID()
var value: Int
// Other properties...
var loc: CGRect = .zero
}
struct ContentView: View {
@State private var ruler: CGFloat!
@State private var items = (0..<10).map { Item(value: $0) }
@State private var centredItem: Item!
var body: some View {
HStack {
if let item = centredItem {
Text("\(item.value)")
}
HStack(spacing: -6) {
Rectangle()
.frame(height: 1)
.measureLoc { loc in
ruler = (loc.minY + loc.maxY) / 2
}
Image(systemName: "powerplug.fill")
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) {
ForEach($items) { $item in
Text("\(item.value)")
.padding()
.frame(width: 80, height: 80)
.background(centredItem != nil &&
centredItem.id == item.id ? .yellow : .white)
.border(.secondary)
.measureLoc { loc in
item.loc = loc
if let ruler = ruler {
if item.loc.maxY >= ruler && item.loc.minY <= ruler {
withAnimation(.easeOut) {
centredItem = item
}
}
// Move outsides
if ruler <= items.first!.loc.minY ||
ruler >= items.last!.loc.maxY {
withAnimation(.easeOut) {
centredItem = nil
}
}
}
}
}
}
// Extra space above and below
.padding(.vertical, ruler)
}
}
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
Detect location:
struct LocKey: PreferenceKey {
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {}
}
extension View {
func measureLoc(_ perform: @escaping (CGRect) ->()) -> some View {
overlay(GeometryReader { geo in
Color.clear
.preference(key: LocKey.self, value: geo.frame(in: .global))
}.onPreferenceChange(LocKey.self, perform: perform))
}
}