markbattistella/swiftdatapager
`SwiftDataPager` is a Swift package designed to simplify the process of implementing pagination with SwiftData.
Installation
Add SwiftDataPager to your Swift project using Swift Package Manager.
dependencies: [
.package(url: "https://github.com/markbattistella/SwiftDataPager", from: "1.0.0")
]Usage
Simple
@PagedQuery(fetchLimit: 20) var movies: [Movie]Advanced
@PagedQuery(
fetchLimit: 10,
sortDescriptors: [SortDescriptor(\Movie.releaseDate, order: .reverse)],
filterPredicate: #Predicate { $0.genre == "Action" },
logger: .default
) var actionMovies: [Movie]View Modifiers
SwiftDataPager comes with several view modifiers to make pagination even easier:
Automatic Loading on Last Item
[!WARNING] The
.onLoadMore(item:, in:)is a required modifier on each cell item. This helps identify when we have reached the limit, and fetch new results. It has been optimised to exit early if not required to fetch.
ForEach(movies) { movie in
MovieRow(movie: movie)
.onLoadMore(item: movie, in: $movies)
}Threshold Loading
Load earlier than the last item:
.onPaginationThreshold(threshold: 3, item: movie, in: $movies)Custom Pagination Triggers
Use your own logic to trigger loadMore():
.onPaginationTrigger(item: movie, in: $movies) { current, all in
current.popularity > 8.0 && all.count > 10
}Loading Indicators
Display a loading spinner during fetch:
if $movies.isFetching {
ProgressView()
.showFetching(in: $movies)
}Auto Load on Appear
Great for empty states:
List {
// List content
}
.onEmptyLoad(in: $movies)Error Handling
SwiftDataPager provides error state tracking to handle fetch failures gracefully:
if let error = $movies.error {
Text("Failed to load: \(error.localizedDescription)")
Button("Retry") { $movies.retry() }
}Resetting Pagination
You can reset pagination to start fresh:
Button("Reset") {
$movies.reset()
}Logging
Toggle and customise logging to see what's going on:
@PagedQuery(fetchLimit: 20, logger: .default) var movies: [Movie]Available logging options:
.none: No logs.default: Logs all entries from the wrapper to console.custom(MyCustomLogger()): Provide your own logging system
[!TIP] You can use your own logging system so you can also send information to crash aggregators or telemetry systems besides logging only to the user's device.
Example
import SwiftUI
import SwiftData
import SwiftDataPager
struct MovieListView: View {
@PagedQuery(
fetchLimit: 100,
sortDescriptors: [.init(\Movie.name)],
filterPredicate: #Predicate { $0.name.contains("AU") },
logger: .default
) private var movies: [Movie]
var body: some View {
NavigationStack {
List {
ForEach(movies) { movie in
Text(movie.name)
.onLoadMore(item: movie, in: $movies)
}
}
.navigationTitle("Movies")
.toolbar {
ToolbarItem(placement: .topBarLeading) {
if $items.isFetching {
ProgressView()
.showFetching(in: $items)
}
}
ToolbarItem(placement: .topBarTrailing) {
if $items.hasReachedEnd {
Text("All done!")
.font(.footnote)
.foregroundStyle(.secondary)
}
}
ToolbarItem(placement: .bottomBar) {
if let error = $items.error {
Text("Error: \(error.localizedDescription)")
.foregroundColor(.red)
}
}
}
}
}
}Video
Demo pagination of 10000 records.
[[SwiftDataPager Demo]](https://www.youtube.com/watch?v=amlm-rkMVTI)
Contributing
Contributions are always welcome! Feel free to submit a pull request or open an issue for any suggestions or improvements you have.
License
SwiftDataPager is licensed under the MIT License. See the LICENCE file for more details.
Package Metadata
Repository: markbattistella/swiftdatapager
Default branch: main
README: README.md