{"id":13778317,"url":"https://github.com/chris-swift-dev/AdvancedList","last_synced_at":"2025-05-11T11:35:26.443Z","repository":{"id":37733933,"uuid":"202424157","full_name":"crelies/AdvancedList","owner":"crelies","description":"Advanced List View for SwiftUI with pagination \u0026 different states","archived":false,"fork":false,"pushed_at":"2022-06-06T19:13:46.000Z","size":5047,"stargazers_count":296,"open_issues_count":2,"forks_count":29,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-14T12:05:17.413Z","etag":null,"topics":["catalyst","ios","listview","mac-os","macos","pagination","pagination-functionality","pagination-support","pagination-swiftui","swift","swift-package","swiftui","swiftui-components","swiftui-example","swiftui-lists"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/crelies.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-08-14T20:51:03.000Z","updated_at":"2024-11-11T13:36:29.000Z","dependencies_parsed_at":"2022-09-15T14:13:04.289Z","dependency_job_id":null,"html_url":"https://github.com/crelies/AdvancedList","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crelies%2FAdvancedList","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crelies%2FAdvancedList/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crelies%2FAdvancedList/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crelies%2FAdvancedList/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/crelies","download_url":"https://codeload.github.com/crelies/AdvancedList/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225043320,"owners_count":17411967,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["catalyst","ios","listview","mac-os","macos","pagination","pagination-functionality","pagination-support","pagination-swiftui","swift","swift-package","swiftui","swiftui-components","swiftui-example","swiftui-lists"],"created_at":"2024-08-03T18:00:52.974Z","updated_at":"2025-05-11T11:35:20.876Z","avatar_url":"https://github.com/crelies.png","language":"Swift","readme":"# AdvancedList\n\n[![Swift 5.3](https://img.shields.io/badge/swift-5.3-green.svg?longCache=true\u0026style=flat-square)](https://developer.apple.com/swift)\n[![Platforms](https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20tvOS-lightgrey.svg?longCache=true\u0026style=flat-square)](https://www.apple.com)\n[![Current version](https://img.shields.io/github/v/tag/crelies/AdvancedList?longCache=true\u0026style=flat-square)](https://github.com/crelies/AdvancedList)\n[![Build status](https://github.com/crelies/AdvancedList/actions/workflows/build.yml/badge.svg)](https://github.com/crelies/AdvancedList/actions/workflows/build.yml)\n[![Code coverage](https://codecov.io/gh/crelies/AdvancedList/branch/dev/graph/badge.svg?token=DhJyoUKNPM)](https://codecov.io/gh/crelies/AdvancedList)\n[![License](https://img.shields.io/badge/license-MIT-lightgrey.svg?longCache=true\u0026style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)\n\nThis package provides a wrapper view around the **SwiftUI** `List view` which adds **pagination** (through my [ListPagination package](https://github.com/crelies/ListPagination)) and an **empty**, **error** and **loading state** including a corresponding view.\n\n## 📦 Installation\n\nAdd this Swift package in Xcode using its Github repository url. (File \u003e Swift Packages \u003e Add Package Dependency...)\n\n## 🚀 How to use\n\nThe `AdvancedList` view is similar to the `List` and `ForEach` views. You have to pass data (`RandomAccessCollection`) and a view provider (`(Data.Element) -\u003e some View`) to the initializer. In addition to the `List` view the `AdvancedList` expects a list state and corresponding views.\nModify your data anytime or hide an item through the content block if you like. The view is updated automatically 🎉.\n\n```swift\nimport AdvancedList\n\n@State private var listState: ListState = .items\n\nAdvancedList(yourData, content: { item in\n    Text(\"Item\")\n}, listState: listState, emptyStateView: {\n    Text(\"No data\")\n}, errorStateView: { error in\n    Text(error.localizedDescription)\n        .lineLimit(nil)\n}, loadingStateView: {\n    Text(\"Loading ...\")\n})\n```\n\n### 🆕 Custom List view\n\nStarting from version `6.0.0` you can use a custom list view instead of the `SwiftUI` `List` used under the hood. As an example you can now easily use the **LazyVStack** introduced in **iOS 14** if needed.\n\nUpgrade from version `5.0.0` **without breaking anything**. Simply add the **listView parameter** after the upgrade:\n\n```swift\nAdvancedList(yourData, listView: { rows in\n    if #available(iOS 14, macOS 11, *) {\n        ScrollView {\n            LazyVStack(alignment: .leading, content: rows)\n                .padding()\n        }\n    } else {\n        List(content: rows)\n    }\n}, content: { item in\n    Text(\"Item\")\n}, listState: listState, emptyStateView: {\n    Text(\"No data\")\n}, errorStateView: { error in\n    Text(error.localizedDescription)\n        .lineLimit(nil)\n}, loadingStateView: {\n    Text(\"Loading ...\")\n})\n```\n\n### 🆕 Custom Content view\n\nStarting from version `8.0.0` you have full freedom \u0026 control over the content view rendered in the `items` state of your `AdvancedList`. Use a `SwiftUI List` or a `custom view`.\n\nUpgrade from version `7.0.0` **without breaking anything** and use the new API:\n\n```swift\nAdvancedList(listState: yourListState, content: {\n    VStack {\n        Text(\"Row 1\")\n        Text(\"Row 2\")\n        Text(\"Row 3\")\n    }\n}, errorStateView: { error in\n    VStack(alignment: .leading) {\n        Text(\"Error\").foregroundColor(.primary)\n        Text(error.localizedDescription).foregroundColor(.secondary)\n    }\n}, loadingStateView: ProgressView.init)\n```\n\n### 📄 Pagination\n\nThe `Pagination` functionality is now (\u003e= `5.0.0`) implemented as a `modifier`.\nIt has three different states: `error`, `idle` and `loading`. If the `state` of the `Pagination` changes the `AdvancedList` displays the view created by the view builder of the specified pagination object (`AdvancedListPagination`). Keep track of the current pagination state by creating a local state variable (`@State`) of type `AdvancedListPaginationState`. Use this state variable in the `content` `ViewBuilder` of your pagination configuration object to determine which view should be displayed in the list (see the example below).\n\nIf you want to use pagination you can choose between the `lastItemPagination` and the `thresholdItemPagination`. Both concepts are described [here](https://github.com/crelies/ListPagination). Just specify the type of the pagination when adding the `.pagination` modifier to your `AdvancedList`.\n\n**The view created by the `content` `ViewBuilder` of your pagination configuration object will only be visible below the List if the last item of the List appeared! That way the user is only interrupted if needed.**\n\n**Example:**\n\n```swift\n@State private var paginationState: AdvancedListPaginationState = .idle\n\nAdvancedList(...)\n.pagination(.init(type: .lastItem, shouldLoadNextPage: {\n    paginationState = .loading\n    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {\n        items.append(contentsOf: moreItems)\n        paginationState = .idle\n    }\n}) {\n    switch paginationState {\n    case .idle:\n        EmptyView()\n    case .loading:\n        if #available(iOS 14, *) {\n            ProgressView()\n        } else {\n            Text(\"Loading ...\")\n        }\n    case let .error(error):\n        Text(error.localizedDescription)\n    }\n})\n```\n\n### 📁 Move and 🗑️ delete items\n\nTo enable the move or delete function just use the related `onMove` or `onDelete` view modifier.\n**Per default the functions are disabled if you don't add the view modifiers.**\n\n```swift\nimport AdvancedList\n\n@State private var listState: ListState = .items\n\nAdvancedList(yourData, content: { item in\n    Text(\"Item\")\n}, listState: listState, emptyStateView: {\n    Text(\"No data\")\n}, errorStateView: { error in\n    Text(error.localizedDescription)\n        .lineLimit(nil)\n}, loadingStateView: {\n    Text(\"Loading ...\")\n})\n.onMove { (indexSet, index) in\n    // move me\n}\n.onDelete { indexSet in\n    // delete me\n}\n```\n\n### 🎛️ Filtering\n\n**You can hide items in your list through the content block.** Only return a view in the content block if a specific condition is met.\n\n## 🎁 Example\n\nThe following code shows how easy-to-use the view is:\n\n```swift\nimport AdvancedList\n\n@State private var listState: ListState = .items\n\nAdvancedList(yourData, content: { item in\n    Text(\"Item\")\n}, listState: listState, emptyStateView: {\n    Text(\"No data\")\n}, errorStateView: { error in\n    VStack {\n        Text(error.localizedDescription)\n            .lineLimit(nil)\n        \n        Button(action: {\n            // do something\n        }) {\n            Text(\"Retry\")\n        }\n    }\n}, loadingStateView: {\n    Text(\"Loading ...\")\n})\n```\n\nFor more examples take a look at the `Example` directory.\n\n## Migration\n\n\u003cdetails\u003e\n\u003csummary\u003eMigration 2.x -\u003e 3.0\u003c/summary\u003e\n\nThe `AdvancedList` was dramatically simplified and is now more like the `List` and `ForEach` SwiftUI views.\n\n1. Delete your list service instances and directly **pass your data to the list initializer**\n2. Create your views through a content block (**initializer parameter**) instead of conforming your items to `View` directly (removed type erased wrapper `AnyListItem`)\n3. Pass a list state binding to the initializer (**before:** the `ListService` managed the list state)\n4. **Move and delete:** Instead of setting `AdvancedListActions` on your list service just pass a `onMoveAction` and/or `onDeleteAction` block to the initializer\n\n**Before:**\n\n```swift\nimport AdvancedList\n\nlet listService = ListService()\nlistService.supportedListActions = .moveAndDelete(onMove: { (indexSet, index) in\n    // please move me\n}, onDelete: { indexSet in\n    // please delete me\n})\nlistService.listState = .loading\n\nAdvancedList(listService: listService, emptyStateView: {\n    Text(\"No data\")\n}, errorStateView: { error in\n    VStack {\n        Text(error.localizedDescription)\n            .lineLimit(nil)\n        \n        Button(action: {\n            // do something\n        }) {\n            Text(\"Retry\")\n        }\n    }\n}, loadingStateView: {\n    Text(\"Loading ...\")\n}, pagination: .noPagination)\n\nlistService.listState = .loading\n// fetch your items ...\nlistService.appendItems(yourItems)\nlistService.listState = .items\n```\n\n**After:**\n\n```swift\nimport AdvancedList\n\n@State private var listState: ListState = .items\n\nAdvancedList(yourData, content: { item in\n    Text(\"Item\")\n}, listState: $listState, onMoveAction: { (indexSet, index) in\n    // move me\n}, onDeleteAction: { indexSet in\n    // delete me\n}, emptyStateView: {\n    Text(\"No data\")\n}, errorStateView: { error in\n    VStack {\n        Text(error.localizedDescription)\n            .lineLimit(nil)\n        \n        Button(action: {\n            // do something\n        }) {\n            Text(\"Retry\")\n        }\n    }\n}, loadingStateView: {\n    Text(\"Loading ...\")\n}, pagination: .noPagination)\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eMigration 3.0 -\u003e 4.0\u003c/summary\u003e\n\nThanks to a hint from @SpectralDragon I could refactor the `onMove` and `onDelete` functionality to view modifiers.\n\n**Before:**\n\n```swift\nimport AdvancedList\n\n@State private var listState: ListState = .items\n\nAdvancedList(yourData, content: { item in\n    Text(\"Item\")\n}, listState: $listState, onMoveAction: { (indexSet, index) in\n    // move me\n}, onDeleteAction: { indexSet in\n    // delete me\n}, emptyStateView: {\n    Text(\"No data\")\n}, errorStateView: { error in\n    VStack {\n        Text(error.localizedDescription)\n            .lineLimit(nil)\n        \n        Button(action: {\n            // do something\n        }) {\n            Text(\"Retry\")\n        }\n    }\n}, loadingStateView: {\n    Text(\"Loading ...\")\n}, pagination: .noPagination)\n```\n\n**After:**\n\n```swift\nimport AdvancedList\n\n@State private var listState: ListState = .items\n\nAdvancedList(yourData, content: { item in\n    Text(\"Item\")\n}, listState: $listState, emptyStateView: {\n    Text(\"No data\")\n}, errorStateView: { error in\n    VStack {\n        Text(error.localizedDescription)\n            .lineLimit(nil)\n        \n        Button(action: {\n            // do something\n        }) {\n            Text(\"Retry\")\n        }\n    }\n}, loadingStateView: {\n    Text(\"Loading ...\")\n}, pagination: .noPagination)\n.onMove { (indexSet, index) in\n    // move me\n}\n.onDelete { indexSet in\n    // delete me\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eMigration 4.0 -\u003e 5.0\u003c/summary\u003e\n\n`Pagination` is now implemented as a `modifier` 💪 And last but not least the code documentation arrived 😀\n\n**Before:**\n\n```swift\nprivate lazy var pagination: AdvancedListPagination\u003cAnyView, AnyView\u003e = {\n    .thresholdItemPagination(errorView: { error in\n        AnyView(\n            VStack {\n                Text(error.localizedDescription)\n                    .lineLimit(nil)\n                    .multilineTextAlignment(.center)\n\n                Button(action: {\n                    // load current page again\n                }) {\n                    Text(\"Retry\")\n                }.padding()\n            }\n        )\n    }, loadingView: {\n        AnyView(\n            VStack {\n                Divider()\n                Text(\"Loading...\")\n            }\n        )\n    }, offset: 25, shouldLoadNextPage: {\n        // load next page\n    }, state: .idle)\n}()\n\n@State private var listState: ListState = .items\n\nAdvancedList(yourData, content: { item in\n    Text(\"Item\")\n}, listState: $listState, emptyStateView: {\n    Text(\"No data\")\n}, errorStateView: { error in\n    VStack {\n        Text(error.localizedDescription)\n            .lineLimit(nil)\n        \n        Button(action: {\n            // do something\n        }) {\n            Text(\"Retry\")\n        }\n    }\n}, loadingStateView: {\n    Text(\"Loading ...\")\n}, pagination: pagination)\n\n```\n\n**After:**\n\n```swift\n@State private var listState: ListState = .items\n@State private var paginationState: AdvancedListPaginationState = .idle\n\nAdvancedList(yourData, content: { item in\n    Text(\"Item\")\n}, listState: $listState, emptyStateView: {\n    Text(\"No data\")\n}, errorStateView: { error in\n    VStack {\n        Text(error.localizedDescription)\n            .lineLimit(nil)\n        \n        Button(action: {\n            // do something\n        }) {\n            Text(\"Retry\")\n        }\n    }\n}, loadingStateView: {\n    Text(\"Loading ...\")\n})\n.pagination(.init(type: .lastItem, shouldLoadNextPage: {\n    paginationState = .loading\n    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {\n        items.append(contentsOf: moreItems)\n        paginationState = .idle\n    }\n}) {\n    switch paginationState {\n    case .idle:\n        EmptyView()\n    case .loading:\n        if #available(iOS 14, *) {\n            ProgressView()\n        } else {\n            Text(\"Loading ...\")\n        }\n    case let .error(error):\n        Text(error.localizedDescription)\n    }\n})\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eMigration 6.0 -\u003e 7.0\u003c/summary\u003e\n\nI replaced the unnecessary listState `Binding` and replaced it with a simple value parameter.\n\n**Before:**\n\n```swift\nimport AdvancedList\n\n@State private var listState: ListState = .items\n\nAdvancedList(yourData, content: { item in\n    Text(\"Item\")\n}, listState: $listState, emptyStateView: {\n    Text(\"No data\")\n}, errorStateView: { error in\n    VStack {\n        Text(error.localizedDescription)\n            .lineLimit(nil)\n        \n        Button(action: {\n            // do something\n        }) {\n            Text(\"Retry\")\n        }\n    }\n}, loadingStateView: {\n    Text(\"Loading ...\")\n}, pagination: .noPagination)\n```\n\n**After:**\n\n```swift\nimport AdvancedList\n\n@State private var listState: ListState = .items\n\nAdvancedList(yourData, content: { item in\n    Text(\"Item\")\n}, listState: listState, emptyStateView: {\n    Text(\"No data\")\n}, errorStateView: { error in\n    VStack {\n        Text(error.localizedDescription)\n            .lineLimit(nil)\n        \n        Button(action: {\n            // do something\n        }) {\n            Text(\"Retry\")\n        }\n    }\n}, loadingStateView: {\n    Text(\"Loading ...\")\n}, pagination: .noPagination)\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eMigration 7.0 -\u003e 8.0\u003c/summary\u003e\nNothing to do 🎉\n\u003c/details\u003e\n","funding_links":[],"categories":["List"],"sub_categories":["Content"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchris-swift-dev%2FAdvancedList","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchris-swift-dev%2FAdvancedList","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchris-swift-dev%2FAdvancedList/lists"}