DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • How to Make a Picture-in-Picture Feature in iOS App Using AVFoundation
  • HLS Streaming With AVKit and Swift UI for iOS and tvOS
  • Automatic 1111: Adding Custom APIs
  • iOS Application Security for Beginners

Trending

  • Unlocking AI Coding Assistants: Generate Unit Tests
  • Agile’s Quarter-Century Crisis
  • Operational Principles, Architecture, Benefits, and Limitations of Artificial Intelligence Large Language Models
  • Unit Testing Large Codebases: Principles, Practices, and C++ Examples
  1. DZone
  2. Coding
  3. Frameworks
  4. Exploring SwiftUI’s ScrollTargetBehavior: Elevating Your UI To The Next Level

Exploring SwiftUI’s ScrollTargetBehavior: Elevating Your UI To The Next Level

The advent of SwiftUI has been a revolution in the world of iOS development, simplifying UI creation with its declarative syntax and powerful features.

By 
Aitor Pagan user avatar
Aitor Pagan
·
Dec. 07, 23 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
1.7K Views

Join the DZone community and get the full member experience.

Join For Free

The advent of SwiftUI has been a revolution in the world of iOS development, simplifying UI creation with its declarative syntax and powerful features. The introduction of ScrollTargetBehavior in SwiftUI is another leap forward, promising to further streamline the development of sophisticated and user-friendly interfaces. This article delves into the nuances of this new feature, exploring how it enhances the development experience and opens up new possibilities in UI design.

The Essence of ScrollTargetBehavior

ScrollTargetBehavior marks a significant enhancement in the way developers can handle scrolling in SwiftUI. It’s not just a new tool; it’s a paradigm shift in creating fluid, intuitive scrollable interfaces. This feature lets developers define how a view behaves when a user scrolls to a specific target within a ScrollView. It’s akin to having a precision tool where you once only had a hammer. Unfortunately, it’s only available from iOS 17.

Key Benefits

  1. Precise scroll control: Developers can now dictate the position of a scrolled-to item within a ScrollView. Whether you want an item to land at the center, leading, or trailing edge, it’s all possible with a simple modifier.
  2. Enhanced user experience: Smooth and predictable scrolling behaviors are crucial for user satisfaction. With ScrollTargetBehavior, achieving this level of refinement is more accessible, leading to interfaces that feel more responsive and natural.
  3. Customizable interactions: Depending on the context of your app, you might need different scrolling behaviors. This new feature affords the flexibility to tailor these behaviors to the specific needs of your application, enhancing both functionality and aesthetics.
  4. Simplified codebase: Implementing complex scrolling logic used to require verbose and intricate code. ScrollTargetBehavior reduces this complexity, allowing for cleaner, more maintainable code.

Real-World Applications

The implications of ScrollTargetBehavior are vast and varied. Here’s a glimpse into what it enables:

Creating Intuitive Galleries and Carousels

Imagine a photo gallery or a product carousel. With ScrollTargetBehavior, developers can ensure that as users swipe through items, each new selection smoothly centers itself on the screen, creating an elegant and seamless browsing experience.

Crafting Educational Content and Tutorials

For apps that offer educational content or step-by-step tutorials, guiding users through material becomes more intuitive. Developers can program the ScrollView to bring focus to each new piece of content in a way that feels organic and deliberate.

Enhancing Navigation in Data-Heavy Apps

In applications where data presentation is key, such as financial dashboards or reporting tools, ScrollTargetBehavior can be used to navigate swiftly to specific data points or sections, making the exploration of complex information more user-friendly.

Implementing a UIPageViewController in SwiftUI

Let us share a custom approach to use this new feature to implement something like our well-known UIPageViewController from UIKit.

PagedIndexViewModifier is a SwiftUI view modifier that allows for creating a paged scroll view, where the content is divided into discrete pages, and the current page index is updated as the user scrolls. Here’s a step-by-step breakdown of implementing this modifier:

Step 1: Define the View Modifier Structure

Start by defining a PagedIndexViewModifier structure that conforms to the ViewModifier protocol.

Swift
 
struct PagedIndexViewModifier: ViewModifier {

    @Binding var index: Int
    @State private var offset: CGPoint = .zero
...
}


  • @Binding var index: This binding allows the modifier to update the current page index.
  • @State private var offset: This state variable tracks the current scroll offset.

Step 2: Implement the Body

The body of the ViewModifier is where the scrolling behavior is defined.

Swift
 
func body(content: Content) -> some View {
  GeometryReader { contentProxy in
        ...
    }
}


  • Use a GeometryReader to measure the size of the content.

Step 3: Configure the ScrollView

Inside the body, set up a ScrollView and a LazyVStack for your content. Giving your content a full width and a full height

Swift
 
ScrollView {
    LazyVStack(spacing: 0) {
        content
        .frame(width: contentProxy.size.width,
               height: contentProxy.size.height)
    }
...
}


  • This creates a vertical scroll view with the content stretched to the full width and height of its container.

Step 4: Track Scroll Offset

Use a GeometryReader within the LazyVStack to track the scroll offset.

Swift
 
.background(GeometryReader { geometry in
        Color.clear
            .preference(key: ScrollOffsetPreferenceKey.self,
                        value: geometry.frame(in: .named("scroll")).origin)
})


  • This invisible background reads the scroll offset and provides it to the enclosing view using a custom PreferenceKey.

Step 5: Update the Current Index

React to changes in the scroll offset to update the current page index.

Swift
 
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
            self.offset = value
            changeIndexIfNeeded(with: value, contentSize: contentProxy.size)
}


  • This callback updates the offset and calls changeIndexIfNeeded to calculate the new page index.

Step 6: Implement Index Change Logic

Define changeIndexIfNeeded to update the index state based on the current scroll position.

Swift
 
private func changeIndexIfNeeded(with scrollValue: CGPoint, contentSize: CGSize) {
    let yPoint = Double(scrollValue.y)
    if yPoint == 0 && index == 0 { return }
    let height = contentSize.height
    guard yPoint.truncatingRemainder(dividingBy: height) == 0  else { return }
    let currentIndex = index
    let expectedIndex = Int(abs(yPoint / (height)))
    if expectedIndex == currentIndex { return }
    index = expectedIndex
}


  • This function computes the current page index from the scroll offset and updates the index binding.

Step 7: Handle Index Changes

Embed your LazyVStack to use onChange to scroll to the new index when it changes.

Swift
 
LazyVStack {
  ScrollViewReader { svReaderProxy in
        ...
        .onChange(of: index) { oldValue, newValue in
                guard oldValue != newValue else { return }
        svReaderProxy.scrollTo(newValue)
    }
}


  • This ensures that when the index is updated externally, the scroll view scrolls to the correct page.

Step 8: Create the Extension

Extend View to use the modifier more conveniently.

Swift
 
extension View {
    func indexedPageView(_ index: Binding<Int>) -> some View {
        modifier(PagedIndexViewModifier(index: index))
    }
}


  • This allows any View to easily adopt the paged scrolling behavior.

Step 9: Define the Preference Key

Finally, define the ScrollOffsetPreferenceKey to pass the scroll offset via preferences.

fileprivate struct ScrollOffsetPreferenceKey: PreferenceKey {
    static var defaultValue: CGPoint = .zero
    static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { }
}


  • This preference key acts as a communication channel between the scroll view and the view modifier.

Making It Work

To see an example of how to use it, let’s share an example code of 10 Hello World pages displaying each index.

Swift
 
struct ContentView: View {
    @State var index: Int = 0
    var body: some View {
        VStack {
            ForEach(1...10, id: \.self) { index in
                VStack {
                    Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                    Text("Hello, world! \(index)")
                }.id(index)
            }
            .indexedPageView($index)
        }
        .ignoresSafeArea()
}


With these steps, you can now create a paged scrolling experience in your SwiftUI applications, complete with lazy loading and index tracking. The PagedIndexViewModifier makes it easy to add this functionality to any scrollable view, enhancing both the aesthetic appeal and user experience of your app.

In case you needed a horizontal scroll, you could add a Binding value to set the ScrollView horizontal axis and a LazyHStack instead. So you could change your layout easily.

To access the complete file with detailed implementation, you can visit the following GitHub link: Complete Swift File on GitHub. Here, you will find the full source code, ready for review and use in your projects.

Conclusion

SwiftUI’s ScrollTargetBehavior is more than just an incremental update; it’s a testament to SwiftUI’s growing capabilities in delivering advanced UI solutions. For developers, it simplifies the creation of complex, interactive, and intuitive interfaces. For users, it elevates the experience of interacting with apps, making them more engaging and easier to navigate. As SwiftUI continues to evolve, features like ScrollTargetBehavior solidify its position as a powerful framework for modern iOS development, empowering developers to create better apps with less effort.

UI Apple iOS application Swift (programming language)

Published at DZone with permission of Aitor Pagan. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • How to Make a Picture-in-Picture Feature in iOS App Using AVFoundation
  • HLS Streaming With AVKit and Swift UI for iOS and tvOS
  • Automatic 1111: Adding Custom APIs
  • iOS Application Security for Beginners

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!