Friday, February 7, 2025
HomeiOS DevelopmentConstructing a stretchy header view with SwiftUI on iOS 18 – Donny...

Constructing a stretchy header view with SwiftUI on iOS 18 – Donny Wals


Revealed on: June 11, 2024

In iOS 18, SwiftUI’s ScrollView has gotten plenty of love. Now we have a number of new options for ScrollView that give tons of management to us as builders. Certainly one of my favourite interactions with scroll views is once I can drag on an inventory an a header picture animates together with it.

In UIKit we might implement a UIScrollViewDelegate and browse the content material offset on scroll. In SwiftUI we may obtain the stretchy header impact with GeometryReader however that is by no means felt like a pleasant answer.

In iOS 18, it is doable to realize a stretchy header with little to no workarounds by utilizing the onScrollGeometryChange view modifier.

To implement this stretchy header I am utilizing the next arrange:

struct StretchingHeaderView: View {
    @State personal var offset: CGFloat = 0

    var physique: some View {
        ZStack(alignment: .prime) {
            Picture(.picture)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .body(peak: 300 + max(0, -offset))
                .clipped()
                .transformEffect(.init(translationX: 0, y: -(max(0, offset))))

            ScrollView {
                Rectangle()
                    .fill(Colour.clear)
                    .body(peak: 300)

                Textual content("(offset)")

                LazyVStack(alignment: .main) {
                    ForEach(0..<100, id: .self) { merchandise in
                        Textual content("Merchandise at (merchandise)")
                    }
                }
            }
            .onScrollGeometryChange(for: CGFloat.self, of: { geo in
                return geo.contentOffset.y + geo.contentInsets.prime
            }, motion: { new, outdated in
                offset = new
            })
        }
    }
}

Now we have an @State personal var to maintain monitor of the ScrollView‘s present content material offset. I am utilizing a ZStack to layer the Picture beneath the ScrollView. I’ve seen that including the Picture to the ScrollView ends in a fairly stuttery animation in all probability as a result of we’ve parts altering measurement whereas the scroll view scrolls. As a substitute, we add a transparent Rectangle to the ScrollView to push or content material down by an applicable quantity.

To make our impact work, we have to improve the picture’s peak by -offset in order that the picture improve when our scroll is destructive. To stop resizing the picture after we’re scrolling down within the record, we use the max operator.

.body(peak: 300 + max(0, -offset))

Subsequent, we additionally have to offset the picture when the consumer scrolls down within the record. Here is what makes that work:

.transformEffect(.init(translationX: 0, y: -(max(0, offset))))

When the offset is optimistic the consumer is scrolling downwards. We need to push our picture up what that occurs. When the offset is destructive, we need to use 0 as a substitute so we once more use the max operator to ensure we do not offset our picture within the flawed route.

To make all of it work, we have to apply the next view modifier to the scroll view:

.onScrollGeometryChange(for: CGFloat.self, of: { geo in
    return geo.contentOffset.y + geo.contentInsets.prime
}, motion: { new, outdated in
    offset = new
})

The onScrollGeometryChange view modifier permits us to specify which sort of worth we intend to calculate primarily based on its geometry. On this case, we’re calculating a CGFloat. This worth could be no matter you need and may match the return kind from the of closure that you simply cross subsequent.

In our case, we have to take the scroll view’s content material offset on the y axis and increment that by the content material inset’s prime. By doing this, we calculate the suitable “zero” level for our impact.

The second closure is the motion that we need to take. We’ll obtain the earlier and the newly calculated worth. For this impact, we need to set our offset variable to be the newly calculated scroll offset.

All this collectively creates a enjoyable strechy and bouncy impact that is tremendous attentive to the consumer’s contact!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments