I'm trying to build custom WheelPicker. All almost done, but....
It's can be scrolled to each element except 2 and 3 for some reason. In the same time I can select any other element. Problem is only with 2 and 3.
Reproduced on iOS and macOS both
Test view:
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
}
}
Custom WheelPicker:
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
}
}
I'm trying to build custom WheelPicker. All almost done, but....
It's can be scrolled to each element except 2 and 3 for some reason. In the same time I can select any other element. Problem is only with 2 and 3.
Reproduced on iOS and macOS both
Test view:
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
}
}
Custom WheelPicker:
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
}
}
Share
Improve this question
edited Mar 12 at 22:57
Andrew
asked Mar 12 at 22:51
AndrewAndrew
11.5k9 gold badges84 silver badges118 bronze badges
1 Answer
Reset to default 2The reason for the problem is that ViewAlignedScrollTargetBehavior
aligns to the top edge of the content. Since you are using a (large) spacing element as the first item, this is stopping it from working properly for items 2 and 3 near the top of the list.
The way to fix is to remove the spacers that are currently inside the ScrollView
and set .contentMargins
on the ScrollView
instead.
Here is the updated view with the changes applied. I also added a horizontal bar in the background, for visual confirmation that it is working correctly:
ScrollViewReader { reader in
ScrollView(.vertical) {
LazyVStack(spacing: 0) { //
暂无评论