I'm trying to implement scroll tracking using scrollTargetLayout and scrollPosition in SwiftUI. My code works as expected when I use LazyHStack, but it doesn't track the scroll position with a regular HStack.
Basically, I've noticed that my views don't always load properly when paging with the LazyHStack, but they appear fine with HStack.
Here's an example that works with HStack:
struct ContentView: View {
@State var pageId: Item.ID?
var items: [Item] = [.init(), .init(), .init(), .init()]
var body: some View {
ZStack {
ScrollView(.horizontal) {
HStack(spacing: 0) {
ForEach(items) { item in
Circle()
.foregroundStyle(.secondary)
.containerRelativeFrame(.horizontal, count: 1, spacing: 0)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.scrollPosition(id: $pageId)
if let pageId {
Text(pageId.uuidString)
.font(.caption)
}
}
}
}
However, if I try to use my own custom view it no longer works to track the scroll position:
struct CircleView: View {
var body: some View {
Circle()
}
}
struct ContentView: View {
@State var pageId: Item.ID?
var items: [Item] = [.init(), .init(), .init(), .init()]
var body: some View {
ZStack {
ScrollView(.horizontal) {
HStack(spacing: 0) {
ForEach(items) { item in
CircleView()
.foregroundStyle(.secondary)
.containerRelativeFrame(.horizontal, count: 1, spacing: 0)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.scrollPosition(id: $pageId)
if let pageId {
Text(pageId.uuidString)
.font(.caption)
}
}
}
}
I've dealt with this last week as well. In my case, I could use LazyHStack just fine, I just didn't want to. There are two scenarios (that I could find) where scrollPosition breaks:
id is down the hierarchy, due to some logic:
VStack{
Text("\(selectedElem ?? 0)").font(.largeTitle)
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing: 30){
ForEach(0...100, id: \.self){ value in
if value % 2 == 0 {
Color(.green)
.frame(width: 10, height:100)
} else {
Color(.pink)
.frame(width: 10, height:100)
}
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $selectedElem)
.scrollTargetBehavior(.viewAligned)
}
Using Custom Views, such as:
VStack{
Text("\(selectedElem ?? 0)").font(.largeTitle)
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing: 30){
ForEach(0...100, id: \.self){ value in
SubView(id: value)
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $selectedElem)
.scrollTargetBehavior(.viewAligned)
}
In either case, .id breaks and it does not get updated when scrolling. SwiftUI is aware of the id, because adding .onAppear modifier and setting selectedElem to 5, for example, will scroll the list at 5.
The solution I have found is to just wrap either of the cases (conditionals or custom Views) in a SwiftUI Container View (Z/H/V)Stack, like this:
VStack{
Text("\(selectedElem ?? 0)").font(.largeTitle)
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing: 30){
ForEach(0...100, id: \.self){ value in
ZStack{
SubView(id: value)
}
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $selectedElem)
.scrollTargetBehavior(.viewAligned)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With