测试视图:
struct TestWheel: View {
@State var t: Int! = 1
var tList = [1,2,3,4,5,6,7,8,9,10,11,12]
var body: some View {
#if os(iOS)
if #available(iOS 17.0, *) {
UksWheelPicker(selected: $t, items: tList, config: UksWheelConfig(itemHeight: 32)) {
Text("Text - \($0)")
.frame(width: 100)
}
}
#endif
#if os(macOS)
if #available(macOS 14.0, *) {
UksWheelPicker(selected: $t, items: tList, config: UksWheelConfig(itemHeight: 32)) {
Text("Text - \($0)")
.frame(width: 100)
}
}
#endif
}
}
the phwheelpicker:
import SwiftUI
import Essentials
@available(macOS 14.0, *)
@available(iOS 17.0, *)
public struct UksWheelPicker<T, ItemView>: View where T: CustomStringConvertible, T: Hashable, ItemView: View {
@Binding var selected: T?
var items: [T]
let config: UksWheelConfig
var itemView: (T) -> ItemView
public init(selected: Binding<T?>, items: [T], config: UksWheelConfig, itemView: @escaping (T) -> ItemView) {
_selected = selected
self.items = items
self.config = config
self.itemView = itemView
}
public var body: some View {
ScrollViewReader { reader in
ScrollView(.vertical) {
LazyVStack(spacing: 0) {
SpacingElem()
.id("_______1__")
ForEach(items, id: \.self) { item in
itemView(item)
.frame(height: config.itemHeight)
.id(item)
.onTapGesture {
withAnimation {
selected = item
}
}
}
SpacingElem()
.id("_______2__")
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.scrollIndicators(.hidden)
.scrollPosition(id: $selected, anchor: .center)
.frame(height: config.itemHeight * 7)
.mask(LinearGradient(gradient: Gradient(colors: [.clear, .clear, .black, .black, .clear, .clear]), startPoint: .top, endPoint: .bottom))
.onAppear {
scrollToNeededElem(reader: reader)
}
}
}
}
//////////////////////
///HELPER VIEWs
/////////////////////
@available(macOS 14.0, *)
@available(iOS 17.0, *)
extension UksWheelPicker {
@ViewBuilder
private func SpacingElem() -> some View {
Spacer()
.frame(width: 100, height: config.itemHeight * 3)
}
}
//////////////////////
///HELPERS
/////////////////////
@available(macOS 14.0, *)
@available(iOS 17.0, *)
extension UksWheelPicker {
func scrollToNeededElem(reader: ScrollViewProxy) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
withAnimation {
if let selected {
reader.scrollTo(selected, anchor: .center)
}
}
}
}
}
public struct UksWheelConfig {
public let itemHeight: CGFloat
public let selectionEnabled: Bool
public let selectionColor: Color?
public let selectionIsOverlay: Bool
public init(itemHeight: CGFloat, selectionEnabled: Bool = true, selectionColor: Color? = nil, selectionIsOverlay: Bool = false) {
self.itemHeight = itemHeight
self.selectionEnabled = selectionEnabled
self.selectionColor = selectionColor
self.selectionIsOverlay = selectionIsOverlay
}
}
这里的问题是,您将
ViewAlignedScrollTargetBehavior
用作滚动目标行为,但是不能假定它与视图的中心保持一致。它实际上与视图顶部保持一致。因此,由于您将(大)间距元素用作第一个项目,因此它可以阻止其在列表顶部附近的项目2和3的正常工作。
即使您在锚固中使用.scrollPosition
,这是无关的,并且不会改变滚动行为的工作方式。
swiftui水平滚动浏览捕获问题在初始负载上ive devevevie ScrolltargetLayout不会中心视图其目标
作为一个解决方案,可以将其作为对这两个帖子中第一个帖子的答案提供的自定义可以改编成垂直滚动视图(这是我的答案):
.center
要使用,需要对visibleItems
的
ScrollTargetBehavior
驱逐
struct StickyMiddlePosition: ScrollTargetBehavior {
let itemHeight: CGFloat
let spacing: CGFloat
let verticalPadding: CGFloat
func updateTarget(_ target: inout ScrollTarget, context: TargetContext) {
// dy is the distance from the target anchor to the
// top edge of a centered item
let dy = (target.anchor?.y ?? 0) == 0
? (context.containerSize.height / 2) - (itemHeight / 2)
: 0
let currentTargetIndex = (target.rect.origin.y + dy - verticalPadding) / (itemHeight + spacing)
let roundedTargetIndex = currentTargetIndex.rounded()
let scrollCorrection = (roundedTargetIndex - currentTargetIndex) * (itemHeight + spacing)
target.rect.origin.y += scrollCorrection
}
}
占位符元素。
aadd垂直填充到body
UksWheelPicker
SpacingElem
滚动目标行为。
更新视图,并应用了更改。我还在背景中添加了一个水平中线,以视觉确认它正常工作:LazyVStack