Under you’ll find an instance view of my drawback. It has one button that, when pressed, will toggle between two scroll views utilizing withAnimation
, setting the scroll place onto the 2nd and third gadgets for both scroll view in onAppear
.
The intent is to have the background of things inside every checklist transition easily from their place in a single checklist, to their place within the different. Nevertheless, this doesn’t look like simply doable when setting the checklist place utilizing an ID/ScrollPosition:
- Initializing a ScrollPosition with the right ID and rendering the ScrollView with that seems to haven’t any impact – the ScrollView shall be drawn on the prime of the scroll contents
- The one approach I’ve discovered to render the ScrollView at an ID place is to scroll to that place in
onAppear
. Nevertheless, it seems that when doing so, thematchedGeometryEffect
interpolates the place of the weather as if thecontentOffset.y
of the ScrollView is briefly 0, leading to an odd impact - The specified animation could be seen if the 2 lists are toggled quickly, permitting for the
matchedGeometryEffect
to clean out the transient y0
and interpolate between the right positions
It appears I both have to
a) make sure the checklist is laid out on the appropriate y location beforehand (very troublesome with dynamic checklist gadgets, however appears to unravel this drawback if setting the y place explicitly)
b) be sure that the checklist is laid out on the appropriate ID beforehand (haven’t been in a position to determine how)
c) make sure the matched geometry impact animation ignores the transient “0” y offset of the ScrollView earlier than setting the ID place in onAppear (haven’t been in a position to determine how)
Notice that I’ve to make use of VStack right here for the matched geometry impact to work constantly.
Any concepts on fixing this?
Code:
import SwiftUI
struct Merchandise: Identifiable {
let id = UUID().uuidString
var top: CGFloat
var label: String
}
enum TestScrollListStyle {
case major
case alternate
}
struct TestScrollList: View {
let gadgets: [Item]
let fashion: TestScrollListStyle
let namespace: Namespace.ID
@Binding var scrollPosition: ScrollPosition
var initialIndex: Int = 2
var physique: some View {
ScrollView {
VStack(spacing: fashion == .major ? 8 : 16) {
ForEach(gadgets, id: .id) { merchandise in
swap fashion {
case .major:
Textual content(merchandise.label)
.body(maxWidth: .infinity)
.body(top: merchandise.top)
.padding(.horizontal)
.background(
Rectangle()
.fill(.blue.opacity(0.15))
.matchedGeometryEffect(id: merchandise.id, in: namespace)
)
case .alternate:
HStack {
Circle()
.fill(.inexperienced.opacity(0.25))
.body(width: 24, top: 24)
Textual content(merchandise.label)
.body(maxWidth: .infinity, alignment: .main)
}
.body(top: merchandise.top)
.padding(.horizontal)
.background(
Rectangle()
.fill(.inexperienced.opacity(0.08))
.matchedGeometryEffect(id: merchandise.id, in: namespace)
)
}
}
}
.scrollTargetLayout()
.padding(.vertical)
}
.scrollPosition($scrollPosition, anchor: .prime)
.onAppear {
var tx = Transaction()
tx.disablesAnimations = true
withTransaction(tx) {
if gadgets.indices.incorporates(initialIndex) {
scrollPosition.scrollTo(id: gadgets[initialIndex].id)
}
}
}
}
}
struct ContentView: View {
@Namespace non-public var matchedNamespace
@State non-public var gadgets: [Item] =
(0..<10).map { i in Merchandise(top: .random(in: 80...220), label: "Row (i)") }
@State non-public var showAlternateView: Bool = false
// Scroll positions for every scroll view
@State non-public var primaryScrollPosition = ScrollPosition(idType: String.self)
@State non-public var alternateScrollPosition = ScrollPosition(idType: String.self)
var physique: some View {
NavigationStack {
ZStack {
if !showAlternateView {
TestScrollList(
gadgets: gadgets,
fashion: .major,
namespace: matchedNamespace,
scrollPosition: $primaryScrollPosition,
initialIndex: 2
)
}
if showAlternateView {
TestScrollList(
gadgets: gadgets,
fashion: .alternate,
namespace: matchedNamespace,
scrollPosition: $alternateScrollPosition,
initialIndex: 3
)
}
}
.navigationTitle("Two ScrollViews + Matched Geometry")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(showAlternateView ? "Major" : "Alternate") {
withAnimation() {
showAlternateView.toggle()
}
}
}
}
}
}
}
#Preview { ContentView() }