InfinityScrollKit
is a SwiftUI package that simplifies implementing infinite scrolling for lists in iOS. The package handles loading more items as the user scrolls, while providing a customizable UI for both normal cells and the last loading/error state cell.
- Infinite scrolling support for any SwiftUI list.
- Native UIKit and AppKit support with
UIInfiniteScrollView
andNSInfiniteScrollView
. - Customizable views for individual items and the last cell (used for progress indicators or error states).
- Support for dynamic loading through a callback for loading more items.
- Encapsulated state management for loading indicators.
- Option to provide feedback to parent views about loading state changes.
- Scroll orientation customization.
- Customizable spacing between cells.
Add the package by going to your Xcode project:
- Select your project in the file navigator.
- Choose the project or target where you want to add the package.
- Go to the Package Dependencies tab.
- Click the
+
button. - Search for
InfinityScrollKit
using the repository URL:https://github.com/PierreJanineh-com/InfinityScrollKit
- Follow the prompts to complete the installation.
-
If you are not yet using CocoaPods in your project, first run
sudo gem install cocoapods
followed bypod init
. (For further information on installing CocoaPods, click here.) -
Add the following to your Podfile (inside the target section):
pod 'InfinityScrollKit'
-
Run
pod install
.
Check out the full example in this repo.
Below is a basic usage example where we create an infinite scroll list with custom item and last cell views:
import SwiftUI
import InfinityScrollKit
struct ContentView: View {
@State private var items: [MyItem] = []
var body: some View {
InfiniteScrollView(
arr: $items,
cellView: { item in
Text(item.name) // Customize the view for each item
}
)
}
}
arr
: The array of items to display.cellView
: AViewBuilder
function to display the individual cells in the list.
For UIKit-based applications, use UIInfiniteScrollView
:
import UIKit
import InfinityScrollKit
class ViewController: UIViewController {
private var scrollView: UIInfiniteScrollView<String, ViewController>!
override func viewDidLoad() {
super.viewDidLoad()
// Initialize with items
scrollView = UIInfiniteScrollView(items: ["Item 1", "Item 2", "Item 3"])
scrollView.delegate = self
view.addSubview(scrollView)
// Setup constraints
// ...
}
}
extension ViewController: UIInfiniteScrollViewDelegate {
func cellFor(_ item: String, at: IndexPath.Index) -> UIView {
let label = UILabel()
label.text = item
return label
}
func lastCellView() -> UIView? {
let activityIndicator = UIActivityIndicatorView(style: .medium)
activityIndicator.startAnimating()
return activityIndicator
}
func emptyArrayView() -> UIView? {
let label = UILabel()
label.text = "No items yet..."
return label
}
func onLoadingChanged(_ isLoading: Bool) {
// Handle loading state changes if needed
}
}
For macOS applications, use NSInfiniteScrollView
:
import AppKit
import InfinityScrollKit
class ViewController: NSViewController {
private var scrollView: NSInfiniteScrollView<String, ViewController>!
override func viewDidLoad() {
super.viewDidLoad()
// Initialize with items
scrollView = NSInfiniteScrollView(items: ["Item 1", "Item 2", "Item 3"])
scrollView.delegate = self
view.addSubview(scrollView)
// Setup constraints
// ...
}
}
extension ViewController: NSInfiniteScrollViewDelegate {
func cellFor(_ item: String, at: IndexPath.Index) -> NSView {
NSTextField(labelWithString: item)
}
func lastCellView() -> NSView? {
let progressIndicator = NSProgressIndicator()
progressIndicator.style = .spinning
progressIndicator.startAnimation(nil)
return progressIndicator
}
func emptyArrayView() -> NSView? {
NSTextField(labelWithString: "No items yet...")
}
func onLoadingChanged(_ isLoading: Bool) {
// Handle loading state changes if needed
}
}
onLoadingChange
: A closure to notify the parent view when the loading state changes.options
: You can passOptions<T>
to customize the number of items per page and handle loading more items via a callback.lastCellView
: AViewBuilder
function for the view at the end of the list (e.g., a loading indicator or error message).emptyArrayView
: Customize the view shown when there are no items in the list.
import SwiftUI
import InfinityScrollKit
struct ContentView: View {
@State private var arr: [String] = []
@State private var isLoading: Bool = false
var body: some View {
InfiniteScrollView(
arr: $arr,
options: options,
onLoadingChanged: onLoadingChanged,
cellView: CellView,
lastCellView: LastCellView,
emptyArrView: EmptyArrView
)
// You can also use ScrollView modifiers directly
.scrollIndicators(.hidden)
}
private var options: Options<String> {
.init(
orientation: .horizontal,
countPerPage: 2,
paginationOptions: .init(
onPageLoad: {
// Replace this with an API pagination request
try? await Task.sleep(nanoseconds: 5 * 1_000_000_000)
var fetchedItems: [String]
// ...
// fetch additional items to add to the current array
return fetchedItems
},
concatMode: .auto //.auto for automatically adding pages to the array instead of passing the full array everytime.
)
)
}
private func onLoadingChanged(_ isLoading: Bool) {
self.isLoading = isLoading
if self.isLoading {
// Do anything here...
}
}
@ViewBuilder func CellView(_ item: String) -> some View {
Text(item)
}
@ViewBuilder func LastCellView() -> some View {
ProgressView()
.padding()
}
@ViewBuilder func EmptyArrView() -> some View {
Text("No items to display...")
}
}
The InfiniteScrollView package supports the following platforms:
- iOS 14.0+
- macOS 14.0+
- watchOS 7.0+
- tvOS 14.0+
- visionOS 1.0+
Feel free to contribute by creating issues or submitting pull requests. Before submitting, make sure to:
- Fork the repository.
- Create your feature branch
(git checkout -b feature/my-feature)
. - Commit your changes
(git commit -m 'Add some feature')
. - Push to the branch
(git push origin feature/my-feature)
. - Open a pull request.
This project is licensed under the MIT License. See the LICENSE file for more details.