使用 .scrollPosition 时带有多个 LazyVGrid 的 ScrollView 会跳转

问题描述 投票:0回答:1

我使用的是 macOS。

我的

LazyVGrid
中有多个
ScrollView
。我正在使用
.scrollPosition()
来跟踪滚动位置。如果不使用此修改器,我不会遇到任何问题,但如果滚动被破坏。

问题与此类似:向ScrollView添加多个LazyVGrid

不幸的是,我不能使用这个解决方案,因为我的网格肯定有不同的高度,这会导致问题。

这是示例代码:

struct ContentView: View {
    
    @State var scrollPosition: UUID? = nil
    
    struct MyStruct: Identifiable {
        let id = UUID()
        let text: String
        let uneven: Bool
    }
        
    @State var elements: [MyStruct]
    
    init() {
        var el = [MyStruct]()
        for i in 1..<30 {
            el.append(MyStruct(text: "Item\(i)", uneven: i % 2 == 0))
        }
        self.elements = el
    }
    
    var body: some View {
        
        VStack {
            
            
            ScrollView(.vertical) {
                
                LazyVStack {
                    
                    ForEach(self.elements) { element in
                        VStack {
                            Text(element.text)
                            LazyVGrid(columns: Array(repeating: GridItem(.fixed(30)), count: 7), content: {
                                ForEach(0..<7 * (element.uneven == true ? 6 : 7), id: \.self) { index in
                                    Text("\(index)")
                                }
                            })
                            .padding(.bottom, 20)
                        }
                    }
                    
                }   // end LazVStack
                .scrollTargetLayout()
                .frame(width: 600)
                
            }
            .scrollPosition(id: self.$scrollPosition)
        }
        
    }
    
}

当您完全向下滚动列表然后向上滚动时,您会遇到此问题。无法再向上滚动。

如果删除

.scrollPosition
修饰符,它就可以工作。如果将
ForEach
行更改为:

ForEach(0..<7 * 7, id: \.self) { index in

它也会起作用,因为这样每个网格都有相同的高度。

添加假行可以解决问题,但也会留下一些额外的空间。添加负的填充或偏移量不会有帮助,它会再次导致问题。

我确信这是 SwiftUI 中的一个错误,我会将其归档。但由于苹果几乎没有修复这些错误,所以有人有解决方法的想法吗?

swiftui scrollview lazyvgrid
1个回答
0
投票

我创建了自己的修改器来跟踪滚动位置,现在它可以工作了(尽管当滚动非常快并且 ScrollView 具有弹跳行为时有时会出现小滞后):

struct ContentView: View {
    
    @State private var offset = CGFloat.zero
    
    struct MyStruct: Identifiable {
        let id = UUID()
        let text: String
        let uneven: Bool
        var height: CGFloat = 0
    }
        
    @State var elements: [MyStruct]
    
    init() {
        var el = [MyStruct]()
        for i in 1..<30 {
            el.append(MyStruct(text: "Item\(i)", uneven: i % 2 == 0))
        }
        self.elements = el
    }
    
    @State var scrollPos: UUID? = nil
    var body: some View {
        
        VStack {
            
            if let scrollPos, let el = self.elements.first(where: { $0.id == scrollPos }) {
                Text(el.text)
            } else {
                Text("NaN")
            }
            
            ScrollView(.vertical) {
                
                VStack {
                    
                    ForEach(self.elements) { element in
                        VStack {
                            Text(element.text)
                            LazyVGrid(columns: Array(repeating: GridItem(.fixed(30)), count: 7), content: {
                                ForEach(0..<7 * (element.uneven == true ? 6 : 7), id: \.self) { index in
                                    Text("\(index)")
                                }
                            })
                        }
                        .scrollPositionTracking(id: element.id)
                    }
                    
                }   // end LazVStack
                .frame(width: 600)
            }
            .scrollPositionTracker(id: self.$scrollPos)
            .coordinateSpace(name: "scroll")
        }
        
    }
    
}


struct ViewOffsetKey: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue = CGFloat.zero
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value += nextValue()
    }
}

@Observable
class ScrollTrackerNegatives {
    var negatives = [UUID: CGFloat]()
}
struct ScrollPositionTracker: ViewModifier {
    
    @State private var negativesStore = ScrollTrackerNegatives()
    var scrollPos: Binding<UUID?>
    
    func body(content: Content) -> some View {
        content
            .environment(self.negativesStore)
            .background(
                GeometryReader { proxy in
                    if self.negativesStore.negatives.count > 0 {
                        if let max = self.negativesStore.negatives.max(by: { $0.value < $1.value }) {
                            Task { @MainActor in
                                self.scrollPos.wrappedValue = max.key
                            }
                        }
                    }
                    return Color.clear
                }
            )
    }
}
struct ScrollPositionTracking: ViewModifier {

    @Environment(ScrollTrackerNegatives.self) private var negativesStore
    let id: UUID
    
    func body(content: Content) -> some View {
        content
            .background(GeometryReader {
                Color.clear.preference(key: ViewOffsetKey.self,
                                       value: $0.frame(in: .named("scroll")).origin.y)
            })
            .onPreferenceChange(ViewOffsetKey.self) { posY in
                if posY < 0 {
                    Task { @MainActor in
                        self.negativesStore.negatives[id] = posY
                    }
                } else {
                    Task { @MainActor in
                        self.negativesStore.negatives.removeValue(forKey: id)
                    }
                }
            }
    }
}

extension View {
    func scrollPositionTracker(id: Binding<UUID?>) -> some View {
        self
            .modifier(ScrollPositionTracker(scrollPos: id))
    }
    func scrollPositionTracking(id: UUID) -> some View {
        self
            .modifier(ScrollPositionTracking(id: id))
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.