SwiftUI - 如何制作可选择的可滚动列表?
SwiftUI - How to make a selectable scrollable list?
我正在尝试制作这样的东西:
滚动之前
滚动后
本质上:
- 项目排列在可滚动列表中
- 位于中间的项目是选中的项目
- 所选项目的属性可访问(通过更新@State 变量)
- 理想情况下,滚动手势是“粘性的”。例如,滚动后最靠近中心的项目重新调整其位置到中心,这样整体排列是一样的。
我尝试过使用 ScrollView,但我不知道如何实现 2 和 4。我想这个想法与 Picker 非常相似?
我已经坚持了一段时间。任何建议将不胜感激。提前致谢!
您可以检测到 select 居中的项目的位置。
// 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: [=10=]) }
@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)
}
}
检测位置:
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))
}
}
我正在尝试制作这样的东西:
滚动之前
滚动后
本质上:
- 项目排列在可滚动列表中
- 位于中间的项目是选中的项目
- 所选项目的属性可访问(通过更新@State 变量)
- 理想情况下,滚动手势是“粘性的”。例如,滚动后最靠近中心的项目重新调整其位置到中心,这样整体排列是一样的。
我尝试过使用 ScrollView,但我不知道如何实现 2 和 4。我想这个想法与 Picker 非常相似?
我已经坚持了一段时间。任何建议将不胜感激。提前致谢!
您可以检测到 select 居中的项目的位置。
// 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: [=10=]) }
@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)
}
}
检测位置:
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))
}
}