Infinite Scrolling in SwiftUI with Real-Time Pagination
Pagination is essential when dealing with large datasets — like a list of users, articles, or images fetched from a server. Instead of loading everything at once, we load content in chunks, improving performance and user experience.
In this blog post, you’ll master how to implement infinite scrolling in SwiftUI using a real-time pagination example. By the end, you’ll have a reusable, modern, and reactive solution that’s perfect for production apps.
🧠 What We’ll Build
We’re going to build a paginated list of posts fetched from a dummy API. As the user scrolls down, more content will load automatically.
Tools:
- ✅ SwiftUI
- ✅ Async/Await
- ✅
@StateObject
and@Published
for state - ✅ Dummy API: jsonplaceholder.typicode.com
🧱 Step 1: Define the Model
struct Post: Identifiable, Codable {
let id: Int
let title: String
let body: String
}
🔌 Step 2: Pagination Service
We’ll create a service that fetches data page by page.
class PostService {
func fetchPosts(page: Int, limit: Int) async throws -> [Post] {
let start = (page - 1) * limit
let url = URL(string: "https://jsonplaceholder.typicode.com/posts?_start=\(start)&_limit=\(limit)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Post].self, from: data)
}
}
🧠 Step 3: ViewModel with Paging Logic
@MainActor
class PostViewModel: ObservableObject {
@Published var posts: [Post] = []
@Published var isLoading = false
private var currentPage = 1
private let limit = 10
private var canLoadMore = true
private let service = PostService()
init() {
Task { await loadMorePosts() }
}
func loadMorePosts() async {
guard !isLoading && canLoadMore else { return }
isLoading = true
do {
let newPosts = try await service.fetchPosts(page: currentPage, limit: limit)
if newPosts.isEmpty {
canLoadMore = false
} else {
posts.append(contentsOf: newPosts)
currentPage += 1
}
} catch {
print("Error: \(error)")
}
isLoading = false
}
}
🧩 Step 4: Build the Paginated View
struct PaginatedPostListView: View {
@StateObject private var viewModel = PostViewModel()
var body: some View {
NavigationView {
List {
ForEach(viewModel.posts) { post in
VStack(alignment: .leading, spacing: 8) {
Text(post.title.capitalized)
.font(.headline)
Text(post.body)
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.vertical, 8)
.onAppear {
if post == viewModel.posts.last {
Task { await viewModel.loadMorePosts() }
}
}
}
if viewModel.isLoading {
HStack {
Spacer()
ProgressView()
Spacer()
}
}
}
.navigationTitle("Paginated Posts")
}
}
}
🔍 How It Works
- Initial Load: When the view model initializes, the first page of posts is loaded.
- Infinite Scrolling: Each post checks if it’s the last one on screen. If yes, it triggers the next fetch.
- Load More: New posts are appended using
append(contentsOf:)
. - Safe Guarding: We prevent duplicate loads using
isLoading
andcanLoadMore
.
🧪 Result Preview
You’ll see a scrollable list of posts, and as you reach the bottom, the next batch loads automatically — smooth and seamless.
💡 Pro Tips
- 🔁 Replace
List
withScrollView + LazyVStack
for custom designs. - ⚠️ Consider throttling if your API rate limits.
- ⛓ Use token-based or cursor-based pagination for more advanced APIs.
📌 Final Thoughts
Congratulations! You’ve just implemented real-time pagination in SwiftUI with infinite scrolling. This technique is invaluable when building modern apps with content feeds, news readers, chat apps, or image galleries.
Comments
Post a Comment