最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - Paged ScrollView miss some items due to scroll process - Stack Overflow

programmeradmin10浏览0评论

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
Add a comment  | 

1 Answer 1

Reset to default 2

The 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) { // 
发布评论

评论列表(0)

  1. 暂无评论