Explore SwiftUI ScrollView Updates from WWDC 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 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())
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)
}
}
}
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)
}
}
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.