Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
196: Handling Pagination in Network Requests
How do I actually track which page I'm on without making a mess of my state?
When you're dealing with a large dataset—say, a list of 1,000 movies from a database—you can't just grab everything at once. You'll kill the user's data plan and likely crash your app's memory. I usually handle this by introducing a state manager or a ViewModel that tracks the currentPage.
Most APIs use either an offset (how many items to skip) or a page number. I prefer page numbers because they're easier to reason about. Here is how I typically structure the request logic:
struct MovieResponse: Codable {
let page: Int
let results: [Movie]
let totalPages: Int
}
class MovieViewModel: ObservableObject {
@Published var movies: [Movie] = []
private var currentPage = 1
private var totalPages = 1
func fetchMovies() async {
// We pass the currentPage to the API call
do {
let response = try await NetworkManager.shared.getMovies(page: currentPage)
self.movies.append(contentsOf: response.results)
self.totalPages = response.totalPages
self.currentPage += 1
} catch {
print("Handle error here")
}
}
}
Notice that I'm using append(contentsOf:). If you just assign the result to your array, you'll wipe out the movies from page one every time you load page two. That's a common mistake I see juniors make.
How do I stop the app from firing five requests for the same page at once?
If you trigger a fetch when the user scrolls to the bottom of a list, you're going to hit a problem: the scroll event fires constantly. If your network request takes a second to return, the user might trigger the "load more" logic ten times in that window. You'll end up with duplicate data and a very angry API server.
The fix is a simple guard flag. I call it isLoading. You check if it's true at the start of the function and set it to true immediately. Only set it back to false once the request is fully finished.
private var isLoading = false
func fetchMovies() async {
guard !isLoading else { return } // Stop if we're already fetching
isLoading = true
defer { isLoading = false } // Ensures this runs even if the function throws
do {
let response = try await NetworkManager.shared.getMovies(page: currentPage)
// ... update movies array ...
} catch {
// handle error
}
}
I use defer here because it's cleaner than writing isLoading = false in both the success and the catch blocks. It's a little Swift trick that keeps the logic tight.
How do I know when I've actually hit the end of the list?
You don't want your app to keep trying to fetch page 11 if the API only has 10 pages of data. It's a waste of resources and creates a poor user experience if you have a loading spinner that just spins forever because the server is returning empty arrays.
The best way to handle this is to track a canLoadMore boolean. I derive this from the totalPages value returned by the API response. If your current page equals or exceeds the total pages, you flip the switch to false and stop making calls.
private var canLoadMore = true
func fetchMovies() async {
guard canLoadMore && !isLoading else { return }
isLoading = true
defer { isLoading = false }
do {
let response = try await NetworkManager.shared.getMovies(page: currentPage)
self.movies.append(contentsOf: response.results)
// If we've reached the end, stop future requests
if currentPage >= response.totalPages {
canLoadMore = false
} else {
currentPage += 1
}
} catch {
// handle error
}
}
Pro tip: In your UI, you can use this canLoadMore flag to hide the loading spinner at the bottom of the list, letting the user know they've truly seen everything.
📋 Practical Task
Exercise: Implementing the Infinite Scroll Guard for a Movie Feed
You have been provided with a MovieViewModel and a MovieAPI client. Currently, the loadNextPage() function is causing duplicate entries in the movie list because it triggers multiple times during scrolling.
Your Task:
- Add an
isLoadingboolean flag to the ViewModel to prevent concurrent network requests. - Implement a
canLoadMorecheck that compares thecurrentPageagainst thetotalPagesreturned in theMovieResponse. - Modify the
loadNextPage()function to guard against bothisLoading == trueandcanLoadMore == falsebefore proceeding with the API call. - Ensure the
isLoadingflag is reset tofalseregardless of whether the network request succeeds or fails.
There are no comments for now.