iOS 17: Unveiling SwiftUI ScrollView Modifiers

Kamal Bhardwaj
9 min readDec 9, 2023

--

Introducing the Exciting Updates in SwiftUI for WWDC23: Elevating the Development and User Experience

The latest from WWDC23 brings a wave of innovative modifiers designed specifically to amplify the power of ScrollView in SwiftUI. These new modifiers are poised to not only streamline the development process but also significantly enhance the overall user experience.

Before delving deeper into these exciting additions, let’s take a moment to reflect on what was lacking in the earlier versions of SwiftUI. While SwiftUI has been a revolutionary framework, certain functionalities, particularly those reminiscent of UIKit’s UICollectionView, were noticeably absent. Developers often found themselves yearning for capabilities such as isPagingEnabled and scrollToPosition — features that provide precise control over content listing and scrolling directions.

In the UIKit realm, UICollectionView has long been a developer favorite, not just for its ability to list content in both horizontal and vertical directions but also for the array of useful APIs it offers. Features like isPagingEnabled and scrollToPosition have empowered developers to fine-tune the user experience with ease.

Now, with the introduction of these new SwiftUI modifiers, we are on the brink of a paradigm shift. No longer constrained by the absence of these essential features, SwiftUI developers can look forward to a more intuitive and efficient development experience. Say goodbye to cumbersome additional code and complex calculations just to determine scroll positions — the future of SwiftUI is here, promising a smoother and more enjoyable journey for both developers and users alike.

What’s New —

Excitingly, Xcode 15 brings forth a treasure trove of new modifiers tailored for ScrollView in SwiftUI, introducing capabilities that promise to elevate your development experience. Let’s take a delightful detour to explore these fresh updates, unveiling the enhanced APIs Apple has generously introduced:

  1. containerRelativeFrame(_:alignment:): This modifier opens up new possibilities for frame management within containers, allowing for precise alignment and positioning.
  2. scrollTargetLayout(isEnabled:): Dive into enhanced control over the scroll target layout, empowering developers to customize and optimize scrolling behaviors with ease.
  3. scrollPosition(id:): This modifier grants the ability to precisely target and manipulate scroll positions based on identifiable elements within your SwiftUI ScrollView.
  4. scrollTransition(): Introduce smooth and visually appealing scroll transitions, enhancing the overall fluidity and user experience in your SwiftUI apps.
  5. safeAreaPadding(): Eliminating Scroll View Padding Woes with .safeAreaPadding
  6. contentMargins(_:): Experience improved control over content margins, offering a more refined and polished appearance for your SwiftUI ScrollView.

Now, the real fun begins! It’s time to roll up our sleeves and get our hands dirty by diving into the practical application of these exciting modifiers. Let’s explore and experiment, unlocking the full potential of SwiftUI in Xcode 15.

Shall we embark on this journey together?

Container Relative Frame —

This modifier is a powerful tool that revolutionizes the way views adapt within their containers. This modifier facilitates the adjustment of a view’s width, height, or both in relation to the size of the container. It goes beyond the basics, allowing customization of dimensions, alignment, and axis orientation within the container.

// Adjust the multiple views wrt container width
func containerRelativeFrame(
_ axes: Axis.Set,
alignment: Alignment = .center,
_ length: @escaping (CGFloat, Axis) -> CGFloat
) -> some View

// Asjust views to achieve the multiple views in different snap size
// wrt to the container.
func containerRelativeFrame(
_ axes: Axis.Set,
count: Int,
span: Int = 1,
spacing: CGFloat,
alignment: Alignment = .center
) -> some View

In the following example of a horizontal scroll of colour rectangle views, we can see how the dimensions change for different cases:

In above example, a list view has 3 scroll view rows.
First row: Before iOS 17, the container and subviews lacked explicit width assignments, resulting in a cluttered UI.
Second row: The modifier introduces a transformative change, enabling the view to inherit the width of the container (in this case, the scroll view width). Here, we demonstrate fixing the height of the view, applying the modifier solely to width adjustments.
Third row: Leveraging the length closure of the modifier, views dynamically adjust their widths to accommodate multiple elements within the container width.

You can even set specific widths for individual elements, prompting the modifier to create paddings with the remaining space. The alignment parameter determines how the view adjusts within the container, with the default setting being center-aligned.

In the provided code screenshot, three distinct examples showcase fixed widths and different alignments. This modifier’s versatility shines through, providing a solution for various layout scenarios.

The second variant proves particularly valuable when the number of views to be scrolled is known, and the objective is to present them in a specific number of paging actions. In the given example, the first case spans two views, causing each subview to expand such a way that content size increase to twice the scroll view’s width. In the second case, all subviews adjust to fit within a single span.

An intriguing aspect to note is the importance of the spacing value. The container considers this value when adjusting subview widths, but do not add spacing itself. So It’s crucial that the inner spacing keyword aligns with the stack’s spacing value to seamlessly fit the span within the container.

Scroll Target Layout —

The scrollTargetLayout modifier, a highly anticipated addition to the SwiftUI toolkit, is a game-changer and a key reason for my deep dive into these new APIs. This update stands out as one of the most significant enhancements for ScrollView.

The potential it brings to the table is nothing short of transformative, particularly for those keen on crafting custom carousels. It needs helping modifier to achieve the it’s action.

// Configures the outermost layout as a scroll target layout. 
func scrollTargetLayout(isEnabled: Bool = true) -> some View

Placing the modifier outside the HStack, providing crucial insights to the compiler regarding the snapping points of your view. This modifier operates with a Boolean variable, isEnabled, defaulted to true, offering precise control over its functionality. It seamlessly pairs with the scrollTargetBehavior modifier applied to the scroll view to define its behaviour.

// Sets the scroll behavior of views scrollable in the provided axes. 
func scrollTargetBehavior(_ behavior: some ScrollTargetBehavior) -> some View

In this example the modifier are added and it include 2 flavours.

Note that the .paging made the scroll offset is as wide as the whole screen despite the size of the scrolled items. To create a paging without strange offsets, you need to set the spacing value 0 for the stack and if UI demands padding, add it to the contained items before the relative container modifier. It’s all in the comments of the code above first item in the VStack.

For an enhanced user experience, consider showcasing both the preceding and succeeding elements of the collection. Achieve this effortlessly by switching the modifier to .viewAligned, creating a visually seamless flow. Experiment with geometry to ensure that the view is elegantly centered, providing users with an engaging and intuitive interface.

private let itemWidth: CGFloat = 300.0
private func scrollView(type: ViewType) -> some View {
GeometryReader { proxy in
ScrollView(.horizontal) {
HStack {
ForEach(colors, id: \.self) { color in
RoundedRectangle(cornerRadius: 25.0)
.fill(color.gradient)
.frame(width: itemWidth, height: 200.0)
}
}
.padding(.horizontal, (proxy.size.width - itemWidth)/2)
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
}
}

Scroll Position —

Introducing the scrollPosition(id:) modifier, a long-awaited addition that brings a new level of control to your SwiftUI scrolling experience. With this modifier, you can now effortlessly keep track of the item currently displayed in the scroll.

Implementation is a breeze — simply provide a binding variable, and voila, the selected item is now under your control. This modifier empowers you with the ability to precisely monitor and respond to the scrolled content, opening up new possibilities for dynamic and responsive interfaces. Say goodbye to the guesswork, and welcome a seamless way to navigate and interact with your scroll views.

Before the introduction of the .scrollPosition(id:) modifier, SwiftUI developers relied on the .id(:) modifier as a go-to solution for binding view identities. This modifier played a crucial role in establishing a connection between the view and its identity, allowing SwiftUI to manage the uniqueness of each view.

In conjunction with this approach, the Scroll View Reader proved to be a valuable tool. Developers utilized keys to identify specific views within the scroll, enabling the ability to scroll and reveal the bound view programmatically. While effective, this approach often required additional workarounds and intricate handling to achieve precise control over the scrolling behaviour.

Scroll Transition —

This feature-rich modifier boasts an array of parameters, offering unparalleled control over the application of effects to the first and last visible items on the screen. With parameters like topLeading and bottomTrailing, developers can configure the scrollTransition to apply distinct effects to these specific positions. The axis parameter further refines the transition behaviour, providing flexibility in defining the axis of the applied effects.

One of the standout aspects of scrollTransition() is its capacity to accept a custom transition closure. This closure, defined by the transition parameter, allows developers to unleash their creativity by specifying the visual effect to be applied along with the transition phase. This level of customization empowers developers to craft stunning and dynamic visual experiences within their ScrollViews.

Safer Area Padding —

Padding within a scroll view can sometimes lead to undesirable visual artifacts, and one common and irksome issue is showcased in the first view of the following example. The good news is that iOS 17 addresses this concern with the introduction of the .safeAreaPadding modifier.

In the provided code example, I experimented with three distinct approaches to adding padding to the scroll view content. Each version presents a unique perspective, offering alternatives to achieve the desired padding effect.

Before the advent of iOS 17, the second approach, as showcased in the video, stood out as an excellent alternative. This approach strategically applied padding to the first subview within the scroll view, delivering a clean and visually appealing result. However, a minor concern surfaced with the scrollIndicator, as it remained visible beyond the scroll view items.

Say goodbye to the headaches associated with scroll view padding, and embrace the simplicity and effectiveness of .safeAreaPadding in iOS 17.

Content Margin —

In our previous discussion, we touched upon a minor concern associated with the earlier approach to padding within a scroll view. However, iOS 17 introduces a solution in the form of the .contentMargins(:) modifier, addressing this concern and providing an additional tool for achieving precise padding configurations.

In the earlier example we discuss a small concern for the earlier approach for padding in scroll view. Other than .safeAreaPadding, iOS 17 came up with another modifier .contentMargin(_ : ) helps to achieve the needful. The optional placement parameter, defaulting to .automatic, further enhances control over the margin placement.

Summary —

In conclusion, the dynamic range of modifiers introduced in iOS 17 for SwiftUI has truly transformed the landscape of scroll view customization. From fine-tuning dimensions with .containerRelativeFrame to orchestrating visually stunning transitions with .scrollTransition , and addressing padding concerns with the likes of .safeAreaPadding and .contentMargins, these tools empower developers to create rich and responsive user interfaces effortlessly.

I hope this exploration into the latest SwiftUI modifiers has been insightful and practical for your development endeavors. Your feedback is invaluable in ensuring that these enhancements align seamlessly with your needs and expectations. Please feel free to share your thoughts, experiences, and any suggestions you may have. Thank you for taking the time to delve into the intricacies of these modifiers, and I look forward to hearing from you. Happy coding!

--

--