ios – How can I render a ScrollView in order that its ScrollPosition is on the appropriate ID throughout format?


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, the matchedGeometryEffect interpolates the place of the weather as if the contentOffset.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 y 0 and interpolate between the right positions

Video

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() }

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles