我使用的是 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 中的一个错误,我会将其归档。但由于苹果几乎没有修复这些错误,所以有人有解决方法的想法吗?
我创建了自己的修改器来跟踪滚动位置,现在它可以工作了(尽管当滚动非常快并且 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))
}
}