Design Converter
Education
Last updated on Jan 28, 2025
Last updated on Jan 28, 2025
Software Development Executive - II
When building apps with SwiftUI, managing large datasets effectively is key to creating a seamless user experience. Pagination is an excellent technique to achieve this, allowing you to load data incrementally as the user scrolls through the content.
This blog explains how to implement pagination in SwiftUI, using a practical example and clean MVVM in SwiftUI architecture.
Pagination breaks large data sets into smaller, more manageable pages. It improves performance, reduces loading times, and enhances usability, especially for scroll-heavy apps like social media feeds or shopping platforms.
In SwiftUI, pagination can be implemented by detecting when the user scrolls to the last row of visible data and then loading additional pages. Let’s explore this step-by-step.
We will use the MVVM in the SwiftUI pattern to separate business logic from the UI. The ViewModel will manage fetching data, tracking the current page, and loading state.
Here’s an example of how to structure the ViewModel:
1import SwiftUI 2 3class PaginationViewModel: ObservableObject { 4 @Published var posts: [String] = [] // Replace `String` with your data model 5 @Published var isLoading: Bool = false 6 @Published var hasError: Bool = false 7 8 private var currentPage: Int = 1 9 private var canLoadMorePages = true 10 11 func fetchData() { 12 guard !isLoading && canLoadMorePages else { return } 13 isLoading = true 14 15 // Simulate a network request 16 DispatchQueue.global().asyncAfter(deadline: .now() + 1) { 17 let newPosts = (1...10).map { "Post \($0 + (self.currentPage - 1) * 10)" } 18 19 DispatchQueue.main.async { 20 self.posts.append(contentsOf: newPosts) 21 self.isLoading = false 22 self.currentPage += 1 23 self.canLoadMorePages = newPosts.count > 0 // Stop if no more pages 24 } 25 } 26 } 27}
The View will display data using ScrollView and detect when the user scrolls to the last row to trigger loading of more pages.
Here’s how to build the View:
1struct PaginationView: View { 2 @StateObject private var viewModel = PaginationViewModel() 3 4 var body: some View { 5 ScrollView { 6 LazyVStack(alignment: .leading, spacing: 16) { 7 ForEach(viewModel.posts, id: \.self) { post in 8 Text(post) 9 .padding() 10 .background(Color.gray.opacity(0.1)) 11 .cornerRadius(8) 12 } 13 14 if viewModel.isLoading { 15 ProgressView() 16 .padding() 17 } 18 } 19 .onAppear { 20 viewModel.fetchData() 21 } 22 .onChange(of: viewModel.posts.count) { _ in 23 loadMoreIfNeeded() 24 } 25 } 26 } 27 28 private func loadMoreIfNeeded() { 29 guard let lastPost = viewModel.posts.last else { return } 30 if viewModel.posts.lastIndex(of: lastPost) == viewModel.posts.count - 1 { 31 viewModel.fetchData() 32 } 33 } 34}
• ScrollView: Renders a vertical list of items.
• LazyVStack: Efficiently manages rows by reusing views that go out of the visible frame.
• ProgressView: Displays a loading indicator during data fetching.
• Detecting the Last Row: This function uses the onChange modifier to monitor post updates and checks when the user scrolls to the last row.
Pagination can also work with horizontal layouts. For example, consider a carousel of images or cards.
Here’s an example using ScrollView with a horizontal LazyHStack:
1struct HorizontalPaginationView: View { 2 @StateObject private var viewModel = PaginationViewModel() 3 4 var body: some View { 5 ScrollView(.horizontal) { 6 LazyHStack(spacing: 16) { 7 ForEach(viewModel.posts, id: \.self) { post in 8 Text(post) 9 .frame(width: 200, height: 150) 10 .background(Color.blue) 11 .cornerRadius(12) 12 .foregroundColor(.white) 13 } 14 15 if viewModel.isLoading { 16 Circle() 17 .fill(Color.gray) 18 .frame(width: 30, height: 30) 19 .padding() 20 } 21 } 22 .padding() 23 .onAppear { 24 viewModel.fetchData() 25 } 26 } 27 } 28}
Loading errors or empty datasets should be gracefully handled to improve the user experience. Here’s how you can manage an error state:
1if viewModel.hasError { 2 Text("An error occurred. Please try again.") 3 .foregroundColor(.red) 4 .padding() 5 .onTapGesture { 6 viewModel.fetchData() 7 } 8}
Add this condition in the main view’s layout to display errors when necessary.
To make your pagination smooth and visually appealing, you can integrate a scrollview snap behavior. This ensures that each item aligns perfectly in the viewport. Use .paging for TabView to achieve this:
1struct SnapPaginationView: View { 2 @StateObject private var viewModel = PaginationViewModel() 3 4 var body: some View { 5 TabView { 6 ForEach(viewModel.posts, id: \.self) { post in 7 Text(post) 8 .frame(width: UIScreen.main.bounds.width, height: 200) 9 .background(Color.purple) 10 .cornerRadius(12) 11 } 12 } 13 .tabViewStyle(PageTabViewStyle()) 14 .onAppear { 15 viewModel.fetchData() 16 } 17 } 18}
Prefetching Data: Fetch the next page of data before the user reaches the last row.
Default Placeholder: Use a placeholder view for rows while data is loading.
Spacing and Layout Adjustments: Set appropriate spacing and padding in the row layout to maintain alignment.
Background Loading: Ensure smooth scrolling by managing background tasks efficiently.
SwiftUI Pagination is a powerful technique for handling large datasets. By combining scroll view, lazy stacks, and MVVM in SwiftUI, you can efficiently load data as the user scrolls. Use this guide as a foundation to implement pagination in your apps, enhancing performance and delivering a smooth experience.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.