Explore SwiftUI ScrollView Updates from WWDC 2023

Farhan Adji
7 min readJul 10, 2023

--

Introduction

SwiftUI’s ScrollView is a powerful tool for displaying large amounts of content in a single view. In WWDC 2023, Apple announced a number of new features for ScrollView that make it even more powerful and flexible.

In this article, we will explore some of the new features in ScrollView including scroll target behavior, scroll position binding, and scroll transition. We will also see how these new features can be used to create more engaging and interactive user interfaces.

Without further ado, let’s dive into the exciting updates brought to SwiftUI ScrollView that is introduced at WWDC 2023.

Scroll target behavior

Scroll target behavior is an exciting new property introduced for ScrollView. With this property, you have the flexibility to define the scrolling behavior when navigating to a specific target. Whether you prefer a smooth scrolling effect or a snappy snap-to-position behavior, the ScrollView can now adapt to your desired scrolling style.

The scroll target behavior can be set using the instance ofScrollTargetBehavior protocol. Currently, there are two ScrollTargetBehaviour instances that you can use.

Paging scroll target behavior
  • paging: scroll target behavior will cause the ScrollView to scroll to the next or previous page, depending on the direction of the scroll
  • viewAligned: scroll target behavior will cause the ScrollView to scroll to the view that is currently aligned with the scroll position.

In addition to the predefined scroll target behaviors, SwiftUI offers the flexibility to create custom ScrollTargetBehavior. We can achieve this by creating a struct that extends the ScrollTargetBehavior protocol. The ScrollTargetBehavior protocol requires you to implement the updateTarget function. This function takes two arguments: the target scroll position and the TargetContext. The TargetContext object provides information about the ScrollView, such as its size and its content.

For example, this custom ScrollTargetBehavior will always scroll the scroll view to a position that is 100 pixels away from the nearest integer value. If the user is scrolling the scroll view to the right, and the current position is 50, the scroll view will be scrolled to a position of -50 or 150.

struct CustomScrollTargetBehavior: ScrollTargetBehavior {
func updateTarget(_ target: inout Target, context: TargetContext) {
if target.rect.origin.x > 0 {
target.rect.origin.x -= 100
} else {
target.rect.origin.x += 100
}
}
}

//Implementation
ScrollView(.horizontal) {
LazyHStack {
ForEach(0..<100) { index in
Text("\(index)")
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(CustomScrollTargetBehavior())
The result of implementing CustomScrollTargetBehavior

If you have noticed, there is a new modifier called scrollTargetLayout. What is its purpose?

Well, scrollTargetLayout a modifier is used to tell the ScrollView which view it should consider for alignment. We need to apply this modifier to layout containers like LazyHStack/VStack within a ScrollView that contains the main repeating content. How about the individual view? You can use scrollTarget modifier instead.

Things to note, The scrollTargetLayout modifier is only applied to the outmost view/stack view that it is applied to. It does not inherit to child view.

LazyHStack { // a scroll target layout
VStack { ... } // not a scroll target layout
LazyHStack { ... } // also not a scroll target layout
}
.scrollTargetLayout()

Scroll position binding

Scroll position binding is a new feature in SwiftUI that allows you to bind the scroll position of a ScrollView to a property. This can be useful for updating the scroll position of a ScrollView in response to changes in another property.

To use scroll position binding, you can add the scrollPosition(id: Binding<(Hashable)?>) modifier to your ScrollView. The scrollPosition(id: Binding<(Hashable)?>) modifier takes a binding as its input. This binding will be updated whenever the scroll position of the ScrollView changes.

For example, the position state variable is updated whenever the user scrolls the ScrollView.

struct ContentView: View {
@State private var position: Int?

var body: some View {
VStack {
Text("Position: \(position ?? 0)")
ScrollView(.vertical) {
LazyVStack(spacing: 10) {
ForEach(0..<100) { index in
Rectangle()
.fill(Color.green.gradient)
.frame(height: 200)
.overlay(Text("\(index)"))
.id(index)
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $position)
}
}
}
The result of using scroll position with binding value

You can also change the binding value of scrollPosition(id:_) modifier to scroll a ScrollView to a specific view ID.

There’s another modifier parameter that can be used to specify the initial scroll position of a ScrollView. The initial scroll position is the position that the ScrollView will scroll to when it is first presented.

The scrollPosition(initialAnchor:) modifier takes a UnitPoint object as its input. You can use the convenience values .top, .center,.bottom, etc or you can customize the x or y position.

struct ContentView: View {
var body: some View {
ScrollView(.vertical) {
LazyVStack(spacing: 10) {
ForEach(0..<100) { index in
Rectangle()
.fill(Color.green.gradient)
.frame(height: 200)
.overlay(Text("\(index)"))
.id(index)
}
}
.scrollTargetLayout()
}
.scrollPosition(initialAnchor: .center)
}
}
The result of using scroll position with initialAncor center

There are numerous use cases where this function can be beneficial. For instance:

  • Scroll to a specific view when a user selects an item from a list.
  • Scroll to the top of a ScrollView when a user taps a “scroll to top” button.
  • Scroll to the bottom of a ScrollView when a user finishes typing in a text field within a chat app.

Scroll transition

Scroll transition is another exciting new feature in SwiftUI that allows you to customize the animation when scrolling between phases of the transition, as the view appears and disappears within the visible region of the containing scroll view. It also allows us to apply visual effects like scale effects, rotating, etc.

To use a scroll transition, you can add thescrollTransition modifier to your target view that will be animated. There are two closure parameters from this modifier. The first one is the view and the second is an instance of the ScrollTransitionPhase type.

ScrollTransitionPhase is an enum that represents the different phases of a scroll transition. These phases are:

  • topLeading: The view is appearing within the visible area at the top edge of a vertical scroll view, or the leading edge of a horizontal scroll view.
  • bottomTrailing: The view is appearing within the visible area at the bottom edge of the vertical scroll view, or the trailing edge of a horizontal scroll view.
  • identity: The view is in the visible area

it also provides another property called value . The value property ranges from -1 to 1 and defines the numeric phase of the transition. A value of -1 means that the view is fully at the top or left of the visible region, a value of 0 means that the view is fully visible, and a value of 1 means that the view is fully at the bottom or right of the visible region.

Let’s jump into the implementation:

struct ContentView: View {
var body: some View {
ScrollView(.vertical) {
LazyVStack(spacing: 10) {
ForEach(0..<100) { index in
Rectangle()
.fill(Color.green.gradient)
.frame(height: 200)
.overlay(Text("\(index)"))
.id(index)
}
.scrollTransition { view, phase in
view
.scaleEffect(phase.isIdentity ? 1 : phase.value)
.blur(radius: phase.isIdentity ? 0 : 10)
.opacity(phase.isIdentity ? 1 : 0.5)
.rotationEffect(rotationAngle(phase))
}
}
}
}

func rotationAngle(_ phase: ScrollTransitionPhase) -> Angle {
switch phase {
case .topLeading:
return .degrees(180)
case .identity:
return .degrees(0)
case .bottomTrailing:
return .degrees(-180)
}
}
}

Here’s the result:

From the code above, we have already used the phase value and specified the rotation degree for each phase. You can also set the transition to customize the animation that is used when scrolling the ScrollView. For example, you could use that to:

  • Change the speed of the animation
  • Add a delay to the animation
  • Use a different type of animation
.scrollTransition(.animated(.spring)) { view, phase in
view
.scaleEffect(phase.isIdentity ? 1 : phase.value)
.blur(radius: phase.isIdentity ? 0 : 10)
.opacity(phase.isIdentity ? 1 : 0.5)
.rotationEffect(rotationAngle(phase))
}

This transition will be applied both while the view is coming into view and while it is disappearing. If you want to specify the transition for each phase you can use topLeading and bottomTrailing input.

.scrollTransition(
topLeading: .identity,
bottomTrailing: .interactive
) { view, phase in
view
.scaleEffect(phase.isIdentity ? 1 : phase.value)
.blur(radius: phase.isIdentity ? 0 : 10)
.opacity(phase.isIdentity ? 1 : 0.5)
.rotationEffect(rotationAngle(phase))
}

And this is the result:

Conclusion

SwiftUI’s ScrollView is a powerful tool for displaying large amounts of content in a single view. In WWDC 2023, Apple announced a number of new features for ScrollView that make it even more powerful and flexible.

In this article, we explored some of the new features in ScrollView including scroll target behavior, scroll position binding, and scroll transition. We also saw how these new features can be used to create more engaging and interactive user interfaces.

I hope this article has inspired you to experiment with the new features in ScrollView. With a little creativity, you can use them to create stunning and user-friendly interfaces for your apps.

--

--

Farhan Adji
Farhan Adji

No responses yet