About Us
dropIcon
IT Services
dropIcon
Technologies
dropIcon
Virtual Staffing
Resources
dropIcon
Technology Trends

Ultimate Guide to Pagination in SwiftUI with ScrollView, LazyVStack, and MVVM

Share :

blogSocial1
blogSocial2
blogSocial3
blogSocial4

Enterprise Mobile App Development

Ultimate Guide to Pagination in SwiftUI with ScrollView, LazyVStack, and MVVM

Pagination is a core feature when working with large data sets in mobile apps, and SwiftUI offers powerful, yet simple tools to implement it effectively. In this comprehensive guide, we’ll go through how to use ScrollView, LazyVStack, and the MVVM architecture to create a paginated list.

By the end, you’ll have a SwiftUI view that loads data as you scroll, ensuring smooth performance and a great user experience. Let’s dive into implementing pagination in SwiftUI!

What is Pagination, and Why Does it Matter?

Pagination helps handle large datasets by breaking them into smaller, more manageable pages. Without pagination, loading an entire data set at once can lead to performance issues and longer loading times, especially on mobile devices.

By using SwiftUI pagination, you can load data incrementally, making your app more efficient and user-friendly.

Real-World Example

Imagine an API that returns 1,000 records at once. Loading all records at once can overwhelm users and slow down performance. Instead, with pagination, we divide this response into 20 pages with 50 records each. This guide will demonstrate how to paginate an API response using SwiftUI’s ScrollView and LazyVStack with the MVVM architecture.

We’ll use the “LIST USERS” API from Reqres, a popular free API for testing, which provides total_pages, per_page, and page parameters along with user data.


Step 1: Setting Up the SwiftUI MVVM Structure

Using MVVM in SwiftUI helps keep your code clean and organized, especially when building paginated views. Here’s a breakdown of the MVVM components for pagination in SwiftUI:

  • Model - Represents the structure of data from the API.
  • ViewModel - Manages data flow between the API and the view.
  • View - Displays the data using SwiftUI’s declarative syntax

Step 2: Creating the User Model

Let’s start with our User model, representing each user and their details from the API. This model will be our single source of truth.

struct User: Identifiable, Decodable {
    let id: Int
    let email: String
    let first_name: String
    let last_name: String
    let avatar: String
}

struct UserResponse: Decodable {
    let page: Int
    let per_page: Int
    let total: Int
    let total_pages: Int
    let data: [User]
}

The UserResponse struct represents the complete response from the API, including pagination metadata like page and total_pages.

Step 3: Creating the ViewModel for Pagination

The ViewModel will manage pagination by tracking the current page and loading new pages as needed. SwiftUI ViewModel here ensures that the data updates are handled effectively, using @Published properties to update the UI automatically.

import Combine

class UsersViewModel: ObservableObject {
    @Published var users: [User] = []
    private var currentPage = 1
    private var totalPages = 1
    private var isLoading = false
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        loadUsers()
    }
    
    func loadUsers() {
        guard !isLoading && currentPage <= totalPages else { return }
        
        isLoading = true
        let urlString = "https://reqres.in/api/users?page=(currentPage)"
        guard let url = URL(string: urlString) else { return }
        
        URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: UserResponse.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { _ in }, receiveValue: { [weak self] response in
                self?.users.append(contentsOf: response.data)
                self?.totalPages = response.total_pages
                self?.currentPage += 1
                self?.isLoading = false
            })
            .store(in: &cancellables)
    }
}

Key Takeaways

  • Current Page Tracking: The ViewModel tracks the current page and stops loading data once all pages are loaded.
  • ObservableObject and Publishers: Using @Published and dataTaskPublisher enables automatic SwiftUI view updates and efficient async loading.

Step 4: Creating the SwiftUI View with Pagination

With our ViewModel in place, we can create the SwiftUI view to display the paginated list of users. Here, ScrollView and LazyVStack work together to create an infinite scroll effect.

import SwiftUI

struct UsersListView: View {
    @StateObject private var viewModel = UsersViewModel()
    
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(viewModel.users) { user in
                    UserView(user: user)
                        .onAppear {
                            if user == viewModel.users.last {
                                viewModel.loadUsers()
                            }
                        }
                }
            }
        }
    }
}

Explanation of Key Lines

  • Lazy Loading with LazyVStack: Using LazyVStack ensures efficient memory usage by only loading views as they appear on the screen.
  • Triggering Pagination on Scroll: With onAppear, we call loadUsers() when the last item becomes visible, fetching the next page.

Step 5: Building the User Cell View

The UserView displays each user’s details, like their profile image, name, and email.

import SwiftUI

struct UserView: View {
    let user: User
    
    var body: some View {
        HStack {
            AsyncImage(url: URL(string: user.avatar))
                .frame(width: 50, height: 50)
                .clipShape(Circle())
            
            VStack(alignment: .leading) {
                Text("\(user.first_name) \(user.last_name)")
                    .font(.headline)
                Text(user.email)
                    .font(.subheadline)
                    .foregroundColor(.gray)
            }
            .padding(.leading, 10)
        }
        .padding()
    }
}

By using AsyncImage, we load user profile images directly from the URL, making it a lightweight and efficient solution for profile images in a paginated list.


How it All Comes Together

With our MVVM setup complete, launching the app will initially display a list of six users. As you scroll down, additional users load seamlessly, creating an infinite scroll effect until the final page of data is reached.


Final Thoughts and Optimization Tips

Using SwiftUI’s LazyVStack for infinite scrolling lists allows you to handle large data sets in a performant way. However, there are a few more things to consider for production-ready apps:

  • Efficient API Calls: Limit API calls by only loading data when necessary and handle loading states effectively.
  • Data Caching: Caching fetched data can significantly improve load times and reduce the number of network calls.
  • Error Handling and Retry Logic: Always include error handling to manage network failures gracefully, allowing users to retry data loading if necessary.

Wrapping Up

With this setup, you now have a scalable, efficient pagination implementation in SwiftUI using MVVM, ScrollView, and LazyVStack.

Hope you enjoyed the article. Happy coding! 👏

fancyCircle
fancyLines
fancyDots

Stay Informed, Stay Ahead

Don’t miss out on the latest updates and expert advice from Perisync. Subscribe to our blog and get fresh content delivered straight to your inbox, so you’re always informed and ready to take on the next big challenge.

Pink Bubbles
Blue Bubbles
Yellow Bubbles
Only at Perisync

Hire Top Engineers for Your Projects

Save up to 70% on hiring costs with Perisync, enabling you to invest more in innovation and growth while accessing elite engineering talent.
alt Image
alt Image
alt Image

Copyright © 2019 - 2024 Perisync Technologies Pvt Ltd. All Rights Reserved. v 0.6.18

CIN: U72900KA2019PTC125482

Dun & Bradstreet's DUNS Number : 877395158


perisync