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

  1. Initial Load: When the view model initializes, the first page of posts is loaded.
  2. Infinite Scrolling: Each post checks if it’s the last one on screen. If yes, it triggers the next fetch.
  3. Load More: New posts are appended using append(contentsOf:).
  4. Safe Guarding: We prevent duplicate loads using isLoading and canLoadMore.

🧪 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 with ScrollView + 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

Popular posts from this blog

Using Core ML with SwiftUI: Build an AI-Powered App

Dependency Injection in iOS with SwiftUI