What is a performant way to change view offset as user scrolls?

I added a background view to my SwiftUI List, and would like to move it up as user scrolls (similar to the effect of that of the Health app). I can't add it onto the background of a List row, because List unconditionally clips content to a row, and I would like the view to extend past a insetted row's bounds (scrollClipDisabled does not work on List). So I added the view as the background view of the entire List.

Currently, I am achieving this by monitoring contentOffset using the new onScrollGeometryChange(for:of:action:) modifier, updating a state variable that controls the offset of the background view. The code looks something like this:


struct ContentView: View {
    @State private var backgroundOffset: CGFloat = 0
    
    var body: some View {
        List {
            ...
        }
        .background {
            backgroundView
                .offset(y: backgroundOffset)
        }
        .onScrollGeometryChange(for: ScrollGeometry.self) { geometry in
            geometry
        } action: { oldValue, newValue in
            let contentOffsetY = newValue.contentOffset.y
            let contentInsetY = newValue.contentInsets.top
            if contentOffsetY <= -contentInsetY {
                backgroundOffset = 0
            } else {
                backgroundOffset = -(contentOffsetY + contentInsetY)
            }
        }
    }
}

However, this results in bad scrolling performance. I am guessing this is due to backgroundOffset being updated too frequently, and thus refreshing the views too often? If so, what is a more performant approach to achieve the desired effect? Thanks!

Answered by DTS Engineer in 798453022

What is a performant way to change view offset as user scrolls?

You'd basically shift the location of a view’s content using offset(x:y:) modifier and update it whenever a user scrolls the view beyond the top of its content. It's a similar logic to what you currently have:


ScrollView {
    // ...
}
.onScrollGeometryChange(for: Bool.self) { geometry in
    geometry.contentOffset.y < geometry.contentInsets.top
} action: { wasBeyondZero, isBeyondZero in
    self.isBeyondZero = isBeyondZero
}

can't add it onto the background of a List row, because List unconditionally clips its content, and I would like the view to extend past the inset grouped list's bounds. So I added the view as the background view of the entire List.

Have you tried using a plain list instead .listStyle(.plain)

What is a performant way to change view offset as user scrolls?

You'd basically shift the location of a view’s content using offset(x:y:) modifier and update it whenever a user scrolls the view beyond the top of its content. It's a similar logic to what you currently have:


ScrollView {
    // ...
}
.onScrollGeometryChange(for: Bool.self) { geometry in
    geometry.contentOffset.y < geometry.contentInsets.top
} action: { wasBeyondZero, isBeyondZero in
    self.isBeyondZero = isBeyondZero
}

can't add it onto the background of a List row, because List unconditionally clips its content, and I would like the view to extend past the inset grouped list's bounds. So I added the view as the background view of the entire List.

Have you tried using a plain list instead .listStyle(.plain)

@DTS Engineer Thanks for the response.

I could use .listStyle(.plain), but I'd still like to maintain the inset grouped style for other rows. I think there is no way to mix-and-match plain and inset grouped styles in the same list?

As I have mentioned, the offset approach seems to cause bad scrolling performance. Any idea why that is?

Try using LazyVStack which does not render elements until they are in the visible area.

ScrollView {
    LazyVStack {
        ForEach(models) { model in
            CardView(model: model)
        }
    }
    .scrollTargetLayout()
}
.onScrollTargetVisibilityChange(for: Model.ID.self, threshold: 0.2) { onScreenCards in
    // Disable video playback for cards that are offscreen...
}

https://developer.apple.com/documentation/swiftui/view/onscrolltargetvisibilitychange(idtype:threshold:_:)

What is a performant way to change view offset as user scrolls?
 
 
Q