Skip to content

Commit 3a1b0eb

Browse files
author
Chris
authored
Merge pull request #1 from crelies/dev
Added move + delete and filter function
2 parents e1a0d0a + 7dd550a commit 3a1b0eb

File tree

5 files changed

+172
-74
lines changed

5 files changed

+172
-74
lines changed

README.md

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,41 @@
11
# AdvancedList
22

33
[![Swift5](https://img.shields.io/badge/swift5-compatible-green.svg?longCache=true&style=flat-square)](https://developer.apple.com/swift)
4-
[![Platform](https://img.shields.io/badge/platform-iOS%20%7C%20macOS-lightgrey.svg?longCache=true&style=flat-square)](https://www.apple.com)
4+
[![Platforms](https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20tvOS-lightgrey.svg?longCache=true&style=flat-square)](https://www.apple.com)
55
[![License](https://img.shields.io/badge/license-MIT-lightgrey.svg?longCache=true&style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)
66

77
This 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.
88

9-
## Installation
9+
## 📦 Installation
1010

1111
Add this Swift package in Xcode using its Github repository url. (File > Swift Packages > Add Package Dependency...)
1212

13-
## How to use
13+
## 🚀 How to use
1414

1515
You control the view through an instance of `ListService`. The service manages the current state and the items of the list.
16-
Use it to append, update or remove items and to modify the state of the list. The view listens to the service and updates itself if needed.
16+
Use it to append, update or remove items and to modify the state of the list. The `AdvancedList` view listens to the service and updates itself if needed.
1717

18-
### Pagination
18+
```swift
19+
import AdvancedList
20+
21+
let listService = ListService()
22+
23+
AdvancedList(listService: listService, emptyStateView: {
24+
Text("No data")
25+
}, errorStateView: { error in
26+
Text(error.localizedDescription)
27+
.lineLimit(nil)
28+
}, loadingStateView: {
29+
Text("Loading ...")
30+
}, pagination: .noPagination)
31+
32+
listService.listState = .loading
33+
// TODO: fetch your items
34+
listService.appendItems(yourItems)
35+
listService.listState = .items
36+
```
37+
38+
### 📄 Pagination
1939

2040
The `Pagination` is implemented as a class (conforming to `ObservableObject`) so the `AdvancedList` can observe it.
2141
It has three different states: `error`, `idle` and `loading`. If the `state` of the `Pagination` changes the `AdvancedList` updates itself to show or hide the state related view (`ErrorView` for state `.error(Error)` or `LoadingView` for state `.loading`, `.idle` will display nothing). Update the `state` if you start loading (`.loading`), stop loading ( `.idle`) or if an error occurred (`.error(Error)`) so the `AdvancedList` can render the appropriate view.
@@ -34,12 +54,71 @@ The `thresholdItemPagination` expects an offset parameter (number of items befor
3454

3555
**Skip pagination setup by using `.noPagination`.**
3656

37-
## Example
57+
**Example:**
58+
59+
```swift
60+
private(set) lazy var pagination: AdvancedListPagination<AnyView, AnyView> = {
61+
.thresholdItemPagination(errorView: { error in
62+
AnyView(
63+
VStack {
64+
Text(error.localizedDescription)
65+
.lineLimit(nil)
66+
.multilineTextAlignment(.center)
67+
68+
Button(action: {
69+
// load current page again
70+
}) {
71+
Text("Retry")
72+
}.padding()
73+
}
74+
)
75+
}, loadingView: {
76+
AnyView(
77+
VStack {
78+
Divider()
79+
Text("Loading...")
80+
}
81+
)
82+
}, offset: 25, shouldLoadNextPage: {
83+
// load next page
84+
}, state: .idle)
85+
}()
86+
```
87+
88+
### 📁 Move and 🗑️ delete items
89+
90+
You can define which actions your list should support through the `supportedListActions` property of your `ListService` instance.
91+
Choose between `delete`, `move`, `moveAndDelete` and `none`. **The default is `none`.**
92+
93+
```swift
94+
let listService = ListService()
95+
listService.supportedListActions = .moveAndDelete(onMove: { indexSet, index in
96+
// move me
97+
}, onDelete: { indexSet in
98+
// please delete me
99+
})
100+
```
101+
102+
### 🎛️ Filtering
103+
104+
The `AdvancedList` supports filtering (**disabled by default**). You only have to set the closure `excludeItem: (AnyListItem) -> Bool)` on your `ListService` instance.
105+
`AnyListItem` gives you access to the item (`Any`). **Keep in mind that you have to cast this item to your custom type!**
106+
107+
```swift
108+
let listService = ListService()
109+
listService.excludeItem = { ($0.item as? YourItem).type == .xyz }
110+
```
111+
112+
## 🎁 Example
38113

39114
The following code shows how easy-to-use the view is:
40115

41116
```swift
117+
import AdvancedList
118+
42119
let listService = ListService()
120+
listService.appendItems(yourItems)
121+
listService.listState = .items
43122

44123
AdvancedList(listService: listService, emptyStateView: {
45124
Text("No data")
@@ -58,3 +137,5 @@ AdvancedList(listService: listService, emptyStateView: {
58137
Text("Loading ...")
59138
}, pagination: .noPagination)
60139
```
140+
141+
For more examples take a look at [AdvancedList-SwiftUI](https://github.com/crelies/AdvancedList-SwiftUI).
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// AdvancedListActions.swift
3+
//
4+
//
5+
// Created by Christian Elies on 09.11.19.
6+
//
7+
8+
import Foundation
9+
10+
public enum AdvancedListActions {
11+
case delete(onDelete: (IndexSet) -> Void)
12+
case move(onMove: (IndexSet, Int) -> Void)
13+
case moveAndDelete(onMove: (IndexSet, Int) -> Void, onDelete: (IndexSet) -> Void)
14+
case none
15+
}

Sources/AdvancedList/public/Models/AnyListItem.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import Foundation
1010
import SwiftUI
1111

1212
public struct AnyListItem: Identifiable, View {
13+
public let item: Any
1314
public let id: AnyHashable
1415
public let body: AnyView
1516

1617
public init<Item: Identifiable>(item: Item) where Item: View {
18+
self.item = item
1719
id = item.id
1820
body = AnyView(item)
1921
}

Sources/AdvancedList/public/Services/ListService.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ public final class ListService: NSObject, ObservableObject {
2424
objectWillChange.send()
2525
}
2626
}
27-
27+
28+
public var supportedListActions: AdvancedListActions = .none
29+
public var excludeItem: (AnyListItem) -> Bool = { _ in false }
30+
2831
public func appendItems<Item: Identifiable>(_ items: [Item]) where Item: View {
2932
let anyListItems = items.map { AnyListItem(item: $0) }
3033
self.items.append(contentsOf: anyListItems)

Sources/AdvancedList/public/Views/AdvancedList.swift

Lines changed: 64 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import ListPagination
1010
import SwiftUI
1111

12-
#if !targetEnvironment(macCatalyst)
1312
public struct AdvancedList<EmptyStateView: View, ErrorStateView: View, LoadingStateView: View, PaginationErrorView: View, PaginationLoadingView: View> : View {
1413
@ObservedObject private var listService: ListService
1514
@ObservedObject private var pagination: AdvancedListPagination<PaginationErrorView, PaginationLoadingView>
@@ -26,27 +25,6 @@ public struct AdvancedList<EmptyStateView: View, ErrorStateView: View, LoadingSt
2625
self.pagination = pagination
2726
}
2827
}
29-
#else
30-
public struct AdvancedList<EmptyStateView: View, ErrorStateView: View, LoadingStateView: View> : View {
31-
private let emptyStateView: () -> EmptyStateView
32-
private let errorStateView: (Error) -> ErrorStateView
33-
private let loadingStateView: () -> LoadingStateView
34-
@State private var isLastItem: Bool = false
35-
36-
@EnvironmentObject var listService: ListService
37-
/*
38-
We have to erase the types of the pagination error and loading view
39-
because currently there is no way to specify the types
40-
*/
41-
@EnvironmentObject var pagination: AdvancedListPagination<AnyView, AnyView>
42-
43-
public init(@ViewBuilder emptyStateView: @escaping () -> EmptyStateView, @ViewBuilder errorStateView: @escaping (Error) -> ErrorStateView, @ViewBuilder loadingStateView: @escaping () -> LoadingStateView) {
44-
self.emptyStateView = emptyStateView
45-
self.errorStateView = errorStateView
46-
self.loadingStateView = loadingStateView
47-
}
48-
}
49-
#endif
5028

5129
extension AdvancedList {
5230
public var body: AnyView {
@@ -59,16 +37,7 @@ extension AdvancedList {
5937
if !listService.items.isEmpty {
6038
return AnyView(
6139
VStack {
62-
List(listService.items) { item in
63-
item
64-
.onAppear {
65-
self.listItemAppears(item)
66-
67-
if self.listService.items.isLastItem(item) {
68-
self.isLastItem = true
69-
}
70-
}
71-
}
40+
getListView()
7241

7342
if isLastItem {
7443
getPaginationStateView()
@@ -104,17 +73,71 @@ extension AdvancedList {
10473
case .noPagination: ()
10574
}
10675
}
76+
77+
private func getListView() -> some View {
78+
switch listService.supportedListActions {
79+
case .delete(let onDelete):
80+
return AnyView(List {
81+
ForEach(listService.items) { item in
82+
if !self.listService.excludeItem(item) {
83+
self.getItemView(item)
84+
}
85+
}.onDelete { indexSet in
86+
onDelete(indexSet)
87+
}
88+
})
89+
case .move(let onMove):
90+
return AnyView(List {
91+
ForEach(listService.items) { item in
92+
if !self.listService.excludeItem(item) {
93+
self.getItemView(item)
94+
}
95+
}.onMove { (indexSet, index) in
96+
onMove(indexSet, index)
97+
}
98+
})
99+
case .moveAndDelete(let onMove, let onDelete):
100+
return AnyView(List {
101+
ForEach(listService.items) { item in
102+
if !self.listService.excludeItem(item) {
103+
self.getItemView(item)
104+
}
105+
}.onMove { (indexSet, index) in
106+
onMove(indexSet, index)
107+
}.onDelete { indexSet in
108+
onDelete(indexSet)
109+
}
110+
})
111+
case .none:
112+
return AnyView(List(listService.items) { item in
113+
if !self.listService.excludeItem(item) {
114+
self.getItemView(item)
115+
}
116+
})
117+
}
118+
}
119+
120+
private func getItemView(_ item: AnyListItem) -> some View {
121+
item
122+
.onAppear {
123+
self.listItemAppears(item)
124+
125+
if self.listService.items.isLastItem(item) {
126+
self.isLastItem = true
127+
}
128+
}
129+
}
107130

108-
private func getPaginationStateView() -> AnyView {
131+
private func getPaginationStateView() -> some View {
109132
var paginationStateView = AnyView(EmptyView())
110133

111134
switch pagination.state {
112-
case .error(let error):
113-
paginationStateView = AnyView(pagination.errorView(error))
114-
case .idle:
115-
paginationStateView = AnyView(EmptyView())
116-
case .loading:
117-
paginationStateView = AnyView(pagination.loadingView())
135+
case .error(let error):
136+
paginationStateView = AnyView(pagination.errorView(error))
137+
case .idle:
138+
paginationStateView = AnyView(EmptyView())
139+
case .loading:
140+
paginationStateView = AnyView(pagination.loadingView())
118141
}
119142

120143
return paginationStateView
@@ -124,39 +147,15 @@ extension AdvancedList {
124147
#if DEBUG
125148
struct AdvancedList_Previews : PreviewProvider {
126149
private static let listService = ListService()
127-
128-
#if targetEnvironment(macCatalyst)
129-
static var previews: some View {
130-
NavigationView {
131-
AdvancedList(emptyStateView: {
132-
Text("No data")
133-
}, errorStateView: { error in
134-
VStack {
135-
Text(error.localizedDescription)
136-
.lineLimit(nil)
137-
138-
Button(action: {
139-
// do something
140-
}) {
141-
Text("Retry")
142-
}
143-
}
144-
}, loadingStateView: {
145-
Text("Loading ...")
146-
})
147-
.environmentObject(listService)
148-
.environmentObject(AdvancedListPagination.noPagination)
149-
}
150-
}
151-
#else
150+
152151
static var previews: some View {
153152
NavigationView {
154153
AdvancedList(listService: listService, emptyStateView: {
155154
Text("No data")
156155
}, errorStateView: { error in
157156
VStack {
158157
Text(error.localizedDescription)
159-
.lineLimit(nil)
158+
.lineLimit(nil)
160159

161160
Button(action: {
162161
// do something
@@ -167,9 +166,7 @@ struct AdvancedList_Previews : PreviewProvider {
167166
}, loadingStateView: {
168167
Text("Loading ...")
169168
}, pagination: .noPagination)
170-
// .navigationBarTitle(Text("List of Items"))
171169
}
172170
}
173-
#endif
174171
}
175172
#endif

0 commit comments

Comments
 (0)