From 0476cc80171b90f364079ea63e985e83b30b3643 Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Thu, 14 Nov 2019 00:28:53 +0100 Subject: [PATCH 1/8] refactor(): replaced objectWillChange with published property wrapper --- .../public/Services/ListService.swift | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Sources/AdvancedList/public/Services/ListService.swift b/Sources/AdvancedList/public/Services/ListService.swift index ba28a84..20506be 100644 --- a/Sources/AdvancedList/public/Services/ListService.swift +++ b/Sources/AdvancedList/public/Services/ListService.swift @@ -11,19 +11,8 @@ import Foundation import SwiftUI public final class ListService: NSObject, ObservableObject { - public let objectWillChange = PassthroughSubject() - - public private(set) var items: [AnyListItem] = [] { - didSet { - objectWillChange.send() - } - } - - public var listState: ListState = .items { - didSet { - objectWillChange.send() - } - } + @Published public private(set) var items: [AnyListItem] = [] + @Published public var listState: ListState = .items public var supportedListActions: AdvancedListActions = .none public var excludeItem: (AnyListItem) -> Bool = { _ in false } From 8b2bbc5418e6dd032f650d686bfffd5107180323 Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Mon, 18 Nov 2019 23:11:30 +0100 Subject: [PATCH 2/8] created alternative implementation similar to the list and foreach swiftui views --- .../public/Views/AdvancedList2.swift | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 Sources/AdvancedList/public/Views/AdvancedList2.swift diff --git a/Sources/AdvancedList/public/Views/AdvancedList2.swift b/Sources/AdvancedList/public/Views/AdvancedList2.swift new file mode 100644 index 0000000..e3e0dc5 --- /dev/null +++ b/Sources/AdvancedList/public/Views/AdvancedList2.swift @@ -0,0 +1,146 @@ +// +// AdvancedList2.swift +// +// +// Created by Christian Elies on 16.11.19. +// + +import ListPagination +import SwiftUI + +public struct AdvancedList2 : View where Data.Element: Identifiable { + @ObservedObject private var pagination: AdvancedListPagination + private var data: Data + private var content: (Data.Element) -> Content + private let listState: Binding + private let emptyStateView: () -> EmptyStateView + private let errorStateView: (Error) -> ErrorStateView + private let loadingStateView: () -> LoadingStateView + @State private var isLastItem: Bool = false + private let supportedListActions: AdvancedListActions + private var excludeItem: (Data.Element) -> Bool + + public init(_ data: Data, @ViewBuilder content: @escaping (Data.Element) -> Content, listState: Binding, supportedListActions: AdvancedListActions = .none, excludeItem: @escaping (Data.Element) -> Bool = { _ in false }, @ViewBuilder emptyStateView: @escaping () -> EmptyStateView, @ViewBuilder errorStateView: @escaping (Error) -> ErrorStateView, @ViewBuilder loadingStateView: @escaping () -> LoadingStateView, pagination: AdvancedListPagination) { + self.data = data + self.content = content + self.listState = listState + self.supportedListActions = supportedListActions + self.excludeItem = excludeItem + self.emptyStateView = emptyStateView + self.errorStateView = errorStateView + self.loadingStateView = loadingStateView + self.pagination = pagination + } +} + +extension AdvancedList2 { + public var body: some View { + switch listState.wrappedValue { + case .error(let error): + return AnyView(errorStateView(error)) + case .items: + if !data.isEmpty { + return AnyView( + VStack { + getListView() + + if isLastItem { + getPaginationStateView() + } + } + ) + } else { + return AnyView(emptyStateView()) + } + case .loading: + return AnyView(loadingStateView()) + } + } +} + +extension AdvancedList2 { + private func getListView() -> some View { + switch supportedListActions { + case .delete(let onDelete): + return AnyView(List { + ForEach(data) { item in + if !self.excludeItem(item) { + self.getItemView(item) + } + }.onDelete { indexSet in + onDelete(indexSet) + } + }) + case .move(let onMove): + return AnyView(List { + ForEach(data) { item in + if !self.excludeItem(item) { + self.getItemView(item) + } + }.onMove { (indexSet, index) in + onMove(indexSet, index) + } + }) + case .moveAndDelete(let onMove, let onDelete): + return AnyView(List { + ForEach(data) { item in + if !self.excludeItem(item) { + self.getItemView(item) + } + }.onMove { (indexSet, index) in + onMove(indexSet, index) + }.onDelete { indexSet in + onDelete(indexSet) + } + }) + case .none: + return AnyView(List(data) { item in + if !self.excludeItem(item) { + self.getItemView(item) + } + }) + } + } + + private func getItemView(_ item: Data.Element) -> some View { + content(item) + .onAppear { + self.listItemAppears(item) + + if self.data.isLastItem(item) { + self.isLastItem = true + } + } + } + + private func listItemAppears(_ item: Data.Element) { + switch pagination.type { + case .lastItem: + if data.isLastItem(item) { + pagination.shouldLoadNextPage() + } + + case .thresholdItem(let offset): + if data.isThresholdItem(offset: offset, + item: item) { + pagination.shouldLoadNextPage() + } + case .noPagination: () + } + } + + private func getPaginationStateView() -> some View { + var paginationStateView = AnyView(EmptyView()) + + switch pagination.state { + case .error(let error): + paginationStateView = AnyView(pagination.errorView(error)) + case .idle: + paginationStateView = AnyView(EmptyView()) + case .loading: + paginationStateView = AnyView(pagination.loadingView()) + } + + return paginationStateView + } +} From bfdecdb967a7657a89e2afb540bc513f86764b6b Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Tue, 19 Nov 2019 21:22:42 +0100 Subject: [PATCH 3/8] refactor(): refactored the view implementation to behave more like the List and ForEach SwiftUI views --- .../public/Views/AdvancedList.swift | 129 ++++++++-------- .../public/Views/AdvancedList2.swift | 146 ------------------ 2 files changed, 67 insertions(+), 208 deletions(-) delete mode 100644 Sources/AdvancedList/public/Views/AdvancedList2.swift diff --git a/Sources/AdvancedList/public/Views/AdvancedList.swift b/Sources/AdvancedList/public/Views/AdvancedList.swift index 68afcdd..a299eaf 100644 --- a/Sources/AdvancedList/public/Views/AdvancedList.swift +++ b/Sources/AdvancedList/public/Views/AdvancedList.swift @@ -9,16 +9,24 @@ import ListPagination import SwiftUI -public struct AdvancedList : View { - @ObservedObject private var listService: ListService +public struct AdvancedList : View where Data.Element: Identifiable { @ObservedObject private var pagination: AdvancedListPagination + private var data: Data + private var content: (Data.Element) -> Content + private var listState: Binding private let emptyStateView: () -> EmptyStateView private let errorStateView: (Error) -> ErrorStateView private let loadingStateView: () -> LoadingStateView @State private var isLastItem: Bool = false - - public init(listService: ListService, @ViewBuilder emptyStateView: @escaping () -> EmptyStateView, @ViewBuilder errorStateView: @escaping (Error) -> ErrorStateView, @ViewBuilder loadingStateView: @escaping () -> LoadingStateView, pagination: AdvancedListPagination) { - self.listService = listService + private let supportedListActions: AdvancedListActions + private var excludeItem: (Data.Element) -> Bool + + public init(_ data: Data, @ViewBuilder content: @escaping (Data.Element) -> Content, listState: Binding, supportedListActions: AdvancedListActions = .none, excludeItem: @escaping (Data.Element) -> Bool = { _ in false }, @ViewBuilder emptyStateView: @escaping () -> EmptyStateView, @ViewBuilder errorStateView: @escaping (Error) -> ErrorStateView, @ViewBuilder loadingStateView: @escaping () -> LoadingStateView, pagination: AdvancedListPagination) { + self.data = data + self.content = content + self.listState = listState + self.supportedListActions = supportedListActions + self.excludeItem = excludeItem self.emptyStateView = emptyStateView self.errorStateView = errorStateView self.loadingStateView = loadingStateView @@ -27,59 +35,37 @@ public struct AdvancedList(_ item: Item) { - switch pagination.type { - case .lastItem: - if listService.items.isLastItem(item) { - pagination.shouldLoadNextPage() - } - - case .thresholdItem(let offset): - if listService.items.isThresholdItem(offset: offset, - item: item) { - pagination.shouldLoadNextPage() - } - case .noPagination: () - } - } - private func getListView() -> some View { - switch listService.supportedListActions { + switch supportedListActions { case .delete(let onDelete): return AnyView(List { - ForEach(listService.items) { item in - if !self.listService.excludeItem(item) { + ForEach(data) { item in + if !self.excludeItem(item) { self.getItemView(item) } }.onDelete { indexSet in @@ -88,8 +74,8 @@ extension AdvancedList { }) case .move(let onMove): return AnyView(List { - ForEach(listService.items) { item in - if !self.listService.excludeItem(item) { + ForEach(data) { item in + if !self.excludeItem(item) { self.getItemView(item) } }.onMove { (indexSet, index) in @@ -98,8 +84,8 @@ extension AdvancedList { }) case .moveAndDelete(let onMove, let onDelete): return AnyView(List { - ForEach(listService.items) { item in - if !self.listService.excludeItem(item) { + ForEach(data) { item in + if !self.excludeItem(item) { self.getItemView(item) } }.onMove { (indexSet, index) in @@ -109,28 +95,44 @@ extension AdvancedList { } }) case .none: - return AnyView(List(listService.items) { item in - if !self.listService.excludeItem(item) { + return AnyView(List(data) { item in + if !self.excludeItem(item) { self.getItemView(item) } }) } } - private func getItemView(_ item: AnyListItem) -> some View { - item + private func getItemView(_ item: Data.Element) -> some View { + content(item) .onAppear { self.listItemAppears(item) - if self.listService.items.isLastItem(item) { + if self.data.isLastItem(item) { self.isLastItem = true } } } - + + private func listItemAppears(_ item: Data.Element) { + switch pagination.type { + case .lastItem: + if data.isLastItem(item) { + pagination.shouldLoadNextPage() + } + + case .thresholdItem(let offset): + if data.isThresholdItem(offset: offset, + item: item) { + pagination.shouldLoadNextPage() + } + case .noPagination: () + } + } + private func getPaginationStateView() -> some View { var paginationStateView = AnyView(EmptyView()) - + switch pagination.state { case .error(let error): paginationStateView = AnyView(pagination.errorView(error)) @@ -139,24 +141,27 @@ extension AdvancedList { case .loading: paginationStateView = AnyView(pagination.loadingView()) } - + return paginationStateView } } #if DEBUG struct AdvancedList_Previews : PreviewProvider { - private static let listService = ListService() + private static let items: [AnyListItem] = [] + @State private static var listState: ListState = .items static var previews: some View { NavigationView { - AdvancedList(listService: listService, emptyStateView: { + AdvancedList(items, content: { element in + Text(element.id.description) + }, listState: $listState, emptyStateView: { Text("No data") }, errorStateView: { error in VStack { Text(error.localizedDescription) .lineLimit(nil) - + Button(action: { // do something }) { diff --git a/Sources/AdvancedList/public/Views/AdvancedList2.swift b/Sources/AdvancedList/public/Views/AdvancedList2.swift deleted file mode 100644 index e3e0dc5..0000000 --- a/Sources/AdvancedList/public/Views/AdvancedList2.swift +++ /dev/null @@ -1,146 +0,0 @@ -// -// AdvancedList2.swift -// -// -// Created by Christian Elies on 16.11.19. -// - -import ListPagination -import SwiftUI - -public struct AdvancedList2 : View where Data.Element: Identifiable { - @ObservedObject private var pagination: AdvancedListPagination - private var data: Data - private var content: (Data.Element) -> Content - private let listState: Binding - private let emptyStateView: () -> EmptyStateView - private let errorStateView: (Error) -> ErrorStateView - private let loadingStateView: () -> LoadingStateView - @State private var isLastItem: Bool = false - private let supportedListActions: AdvancedListActions - private var excludeItem: (Data.Element) -> Bool - - public init(_ data: Data, @ViewBuilder content: @escaping (Data.Element) -> Content, listState: Binding, supportedListActions: AdvancedListActions = .none, excludeItem: @escaping (Data.Element) -> Bool = { _ in false }, @ViewBuilder emptyStateView: @escaping () -> EmptyStateView, @ViewBuilder errorStateView: @escaping (Error) -> ErrorStateView, @ViewBuilder loadingStateView: @escaping () -> LoadingStateView, pagination: AdvancedListPagination) { - self.data = data - self.content = content - self.listState = listState - self.supportedListActions = supportedListActions - self.excludeItem = excludeItem - self.emptyStateView = emptyStateView - self.errorStateView = errorStateView - self.loadingStateView = loadingStateView - self.pagination = pagination - } -} - -extension AdvancedList2 { - public var body: some View { - switch listState.wrappedValue { - case .error(let error): - return AnyView(errorStateView(error)) - case .items: - if !data.isEmpty { - return AnyView( - VStack { - getListView() - - if isLastItem { - getPaginationStateView() - } - } - ) - } else { - return AnyView(emptyStateView()) - } - case .loading: - return AnyView(loadingStateView()) - } - } -} - -extension AdvancedList2 { - private func getListView() -> some View { - switch supportedListActions { - case .delete(let onDelete): - return AnyView(List { - ForEach(data) { item in - if !self.excludeItem(item) { - self.getItemView(item) - } - }.onDelete { indexSet in - onDelete(indexSet) - } - }) - case .move(let onMove): - return AnyView(List { - ForEach(data) { item in - if !self.excludeItem(item) { - self.getItemView(item) - } - }.onMove { (indexSet, index) in - onMove(indexSet, index) - } - }) - case .moveAndDelete(let onMove, let onDelete): - return AnyView(List { - ForEach(data) { item in - if !self.excludeItem(item) { - self.getItemView(item) - } - }.onMove { (indexSet, index) in - onMove(indexSet, index) - }.onDelete { indexSet in - onDelete(indexSet) - } - }) - case .none: - return AnyView(List(data) { item in - if !self.excludeItem(item) { - self.getItemView(item) - } - }) - } - } - - private func getItemView(_ item: Data.Element) -> some View { - content(item) - .onAppear { - self.listItemAppears(item) - - if self.data.isLastItem(item) { - self.isLastItem = true - } - } - } - - private func listItemAppears(_ item: Data.Element) { - switch pagination.type { - case .lastItem: - if data.isLastItem(item) { - pagination.shouldLoadNextPage() - } - - case .thresholdItem(let offset): - if data.isThresholdItem(offset: offset, - item: item) { - pagination.shouldLoadNextPage() - } - case .noPagination: () - } - } - - private func getPaginationStateView() -> some View { - var paginationStateView = AnyView(EmptyView()) - - switch pagination.state { - case .error(let error): - paginationStateView = AnyView(pagination.errorView(error)) - case .idle: - paginationStateView = AnyView(EmptyView()) - case .loading: - paginationStateView = AnyView(pagination.loadingView()) - } - - return paginationStateView - } -} From 44b7654e7b58dfe0117e68494de9e78d6ccd3e2b Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Tue, 19 Nov 2019 21:29:04 +0100 Subject: [PATCH 4/8] refactor(): removed any list item and list service --- .../public/Models/AnyListItem.swift | 22 --------- .../public/Services/ListService.swift | 46 ------------------- 2 files changed, 68 deletions(-) delete mode 100644 Sources/AdvancedList/public/Models/AnyListItem.swift delete mode 100644 Sources/AdvancedList/public/Services/ListService.swift diff --git a/Sources/AdvancedList/public/Models/AnyListItem.swift b/Sources/AdvancedList/public/Models/AnyListItem.swift deleted file mode 100644 index 7ed3856..0000000 --- a/Sources/AdvancedList/public/Models/AnyListItem.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// AnyListItem.swift -// AdvancedList -// -// Created by Christian Elies on 12.07.19. -// Copyright © 2019 Christian Elies. All rights reserved. -// - -import Foundation -import SwiftUI - -public struct AnyListItem: Identifiable, View { - public let item: Any - public let id: AnyHashable - public let body: AnyView - - public init(item: Item) where Item: View { - self.item = item - id = item.id - body = AnyView(item) - } -} diff --git a/Sources/AdvancedList/public/Services/ListService.swift b/Sources/AdvancedList/public/Services/ListService.swift deleted file mode 100644 index 20506be..0000000 --- a/Sources/AdvancedList/public/Services/ListService.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// ListService.swift -// AdvancedList -// -// Created by Christian Elies on 01.07.19. -// Copyright © 2019 Christian Elies. All rights reserved. -// - -import Combine -import Foundation -import SwiftUI - -public final class ListService: NSObject, ObservableObject { - @Published public private(set) var items: [AnyListItem] = [] - @Published public var listState: ListState = .items - - public var supportedListActions: AdvancedListActions = .none - public var excludeItem: (AnyListItem) -> Bool = { _ in false } - - public func appendItems(_ items: [Item]) where Item: View { - let anyListItems = items.map { AnyListItem(item: $0) } - self.items.append(contentsOf: anyListItems) - } - - public func updateItems(_ items: [Item]) where Item: View { - let anyListItems = items.map { AnyListItem(item: $0) } - for anyListItem in anyListItems { - guard let itemIndex = self.items.firstIndex(where: { $0.id == anyListItem.id }) else { - continue - } - - self.items[itemIndex] = anyListItem - } - } - - public func removeItems(_ items: [Item]) where Item: View { - let anyListItemsToRemove = items.map { AnyListItem(item: $0) } - self.items.removeAll(where: { item in - return anyListItemsToRemove.contains { item.id == $0.id } - }) - } - - public func removeAllItems() { - items.removeAll() - } -} From 21f2e26483b123ef3c6cbed4734077e2c0323170 Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Wed, 20 Nov 2019 00:09:17 +0100 Subject: [PATCH 5/8] refactor(): removed list actions enum, created closures for move + delete and simplified list actions logic, removed exclude item logic --- .../public/Models/AdvancedListActions.swift | 15 ----- .../public/Views/AdvancedList.swift | 65 ++++++------------- 2 files changed, 19 insertions(+), 61 deletions(-) delete mode 100644 Sources/AdvancedList/public/Models/AdvancedListActions.swift diff --git a/Sources/AdvancedList/public/Models/AdvancedListActions.swift b/Sources/AdvancedList/public/Models/AdvancedListActions.swift deleted file mode 100644 index 9459047..0000000 --- a/Sources/AdvancedList/public/Models/AdvancedListActions.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// AdvancedListActions.swift -// -// -// Created by Christian Elies on 09.11.19. -// - -import Foundation - -public enum AdvancedListActions { - case delete(onDelete: (IndexSet) -> Void) - case move(onMove: (IndexSet, Int) -> Void) - case moveAndDelete(onMove: (IndexSet, Int) -> Void, onDelete: (IndexSet) -> Void) - case none -} diff --git a/Sources/AdvancedList/public/Views/AdvancedList.swift b/Sources/AdvancedList/public/Views/AdvancedList.swift index a299eaf..a38d62e 100644 --- a/Sources/AdvancedList/public/Views/AdvancedList.swift +++ b/Sources/AdvancedList/public/Views/AdvancedList.swift @@ -10,23 +10,26 @@ import ListPagination import SwiftUI public struct AdvancedList : View where Data.Element: Identifiable { + public typealias OnMoveAction = Optional<(IndexSet, Int) -> Void> + public typealias OnDeleteAction = Optional<(IndexSet) -> Void> + @ObservedObject private var pagination: AdvancedListPagination private var data: Data private var content: (Data.Element) -> Content private var listState: Binding + private var onMoveAction: OnMoveAction = nil + private var onDeleteAction: OnDeleteAction = nil private let emptyStateView: () -> EmptyStateView private let errorStateView: (Error) -> ErrorStateView private let loadingStateView: () -> LoadingStateView @State private var isLastItem: Bool = false - private let supportedListActions: AdvancedListActions - private var excludeItem: (Data.Element) -> Bool - public init(_ data: Data, @ViewBuilder content: @escaping (Data.Element) -> Content, listState: Binding, supportedListActions: AdvancedListActions = .none, excludeItem: @escaping (Data.Element) -> Bool = { _ in false }, @ViewBuilder emptyStateView: @escaping () -> EmptyStateView, @ViewBuilder errorStateView: @escaping (Error) -> ErrorStateView, @ViewBuilder loadingStateView: @escaping () -> LoadingStateView, pagination: AdvancedListPagination) { + public init(_ data: Data, @ViewBuilder content: @escaping (Data.Element) -> Content, listState: Binding, onMoveAction: OnMoveAction = nil, onDeleteAction: OnDeleteAction = nil, @ViewBuilder emptyStateView: @escaping () -> EmptyStateView, @ViewBuilder errorStateView: @escaping (Error) -> ErrorStateView, @ViewBuilder loadingStateView: @escaping () -> LoadingStateView, pagination: AdvancedListPagination) { self.data = data self.content = content self.listState = listState - self.supportedListActions = supportedListActions - self.excludeItem = excludeItem + self.onMoveAction = onMoveAction + self.onDeleteAction = onDeleteAction self.emptyStateView = emptyStateView self.errorStateView = errorStateView self.loadingStateView = loadingStateView @@ -61,45 +64,11 @@ extension AdvancedList { extension AdvancedList { private func getListView() -> some View { - switch supportedListActions { - case .delete(let onDelete): - return AnyView(List { - ForEach(data) { item in - if !self.excludeItem(item) { - self.getItemView(item) - } - }.onDelete { indexSet in - onDelete(indexSet) - } - }) - case .move(let onMove): - return AnyView(List { - ForEach(data) { item in - if !self.excludeItem(item) { - self.getItemView(item) - } - }.onMove { (indexSet, index) in - onMove(indexSet, index) - } - }) - case .moveAndDelete(let onMove, let onDelete): - return AnyView(List { - ForEach(data) { item in - if !self.excludeItem(item) { - self.getItemView(item) - } - }.onMove { (indexSet, index) in - onMove(indexSet, index) - }.onDelete { indexSet in - onDelete(indexSet) - } - }) - case .none: - return AnyView(List(data) { item in - if !self.excludeItem(item) { - self.getItemView(item) - } - }) + List { + ForEach(data) { item in + self.getItemView(item) + }.onMove(perform: self.onMoveAction) + .onDelete(perform: self.onDeleteAction) } } @@ -146,15 +115,19 @@ extension AdvancedList { } } +extension UUID: Identifiable { + public var id: String { uuidString } +} + #if DEBUG struct AdvancedList_Previews : PreviewProvider { - private static let items: [AnyListItem] = [] + private static let items: [UUID] = [] @State private static var listState: ListState = .items static var previews: some View { NavigationView { AdvancedList(items, content: { element in - Text(element.id.description) + Text(element.id) }, listState: $listState, emptyStateView: { Text("No data") }, errorStateView: { error in From a551a8ef5b04520046aa22492c6febb9c00e2a75 Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Wed, 20 Nov 2019 00:11:09 +0100 Subject: [PATCH 6/8] refactor(): removed unnecessary extension for preview --- Sources/AdvancedList/public/Views/AdvancedList.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/AdvancedList/public/Views/AdvancedList.swift b/Sources/AdvancedList/public/Views/AdvancedList.swift index a38d62e..9aaa93e 100644 --- a/Sources/AdvancedList/public/Views/AdvancedList.swift +++ b/Sources/AdvancedList/public/Views/AdvancedList.swift @@ -115,13 +115,13 @@ extension AdvancedList { } } -extension UUID: Identifiable { - public var id: String { uuidString } -} - #if DEBUG struct AdvancedList_Previews : PreviewProvider { - private static let items: [UUID] = [] + private struct MockItem: Identifiable { + let id: String = UUID().uuidString + } + + private static let items: [MockItem] = [] @State private static var listState: ListState = .items static var previews: some View { From 9bf91fcf5393dc3314a3a5b3b3ffede716a9bfdf Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Wed, 20 Nov 2019 00:51:23 +0100 Subject: [PATCH 7/8] docs(readme): updated readme to reflect the recent refactoring --- README.md | 129 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 7a47569..f591985 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,17 @@ Add this Swift package in Xcode using its Github repository url. (File > Swift P ## 🚀 How to use -You control the view through an instance of `ListService`. The service manages the current state and the items of the list. -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. +The `AdvancedList` view is similar to the `List` and `ForEach` views. You have to pass data (`RandomAccessCollection`) and a view provider (`(Data.Element) -> some View`) to the initializer. In addition to the `List` view the `AdvancedList` expects a list state and corresponding views. +Modify your data anytime or hide an item through the content block if you like. The view is updated automatically 🎉. ```swift import AdvancedList -let listService = ListService() +@State private var listState: ListState = .items -AdvancedList(listService: listService, emptyStateView: { +AdvancedList(yourData, content: { item in + Text("Item") +}, listState: $listState, emptyStateView: { Text("No data") }, errorStateView: { error in Text(error.localizedDescription) @@ -28,11 +30,6 @@ AdvancedList(listService: listService, emptyStateView: { }, loadingStateView: { Text("Loading ...") }, pagination: .noPagination) - -listService.listState = .loading -// TODO: fetch your items -listService.appendItems(yourItems) -listService.listState = .items ``` ### 📄 Pagination @@ -87,38 +84,85 @@ private(set) lazy var pagination: AdvancedListPagination = { ### 📁 Move and 🗑️ delete items -You can define which actions your list should support through the `supportedListActions` property of your `ListService` instance. -Choose between `delete`, `move`, `moveAndDelete` and `none`. **The default is `none`.** +You can define which actions your list should support through the `onMoveAction` and `onDeleteAction` initializer parameters. +**Per default the move and delete function is disabled.** ```swift -let listService = ListService() -listService.supportedListActions = .moveAndDelete(onMove: { indexSet, index in - // move me -}, onDelete: { indexSet in - // please delete me -}) +import AdvancedList + +@State private var listState: ListState = .items + +AdvancedList(yourData, content: { item in + Text("Item") +}, listState: $listState, onMoveAction: { (indexSet, index) in + // do something +}, onDeleteAction: { indexSet in + // do something +}, emptyStateView: { + Text("No data") +}, errorStateView: { error in + Text(error.localizedDescription) + .lineLimit(nil) +}, loadingStateView: { + Text("Loading ...") +}, pagination: .noPagination) ``` ### 🎛️ Filtering -The `AdvancedList` supports filtering (**disabled by default**). You only have to set the closure `excludeItem: (AnyListItem) -> Bool)` on your `ListService` instance. -`AnyListItem` gives you access to the item (`Any`). **Keep in mind that you have to cast this item to your custom type!** +**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. + +## 🎁 Example + +The following code shows how easy-to-use the view is: ```swift -let listService = ListService() -listService.excludeItem = { ($0.item as? YourItem).type == .xyz } +import AdvancedList + +@State private var listState: ListState = .items + +AdvancedList(yourData, content: { item in + Text("Item") +}, listState: $listState, emptyStateView: { + Text("No data") +}, errorStateView: { error in + VStack { + Text(error.localizedDescription) + .lineLimit(nil) + + Button(action: { + // do something + }) { + Text("Retry") + } + } +}, loadingStateView: { + Text("Loading ...") +}, pagination: .noPagination) ``` -## 🎁 Example +For more examples take a look at [AdvancedList-SwiftUI](https://github.com/crelies/AdvancedList-SwiftUI). -The following code shows how easy-to-use the view is: +## Migration 2.x -> 3.0 + +The `AdvancedList` was dramatically simplified and is now more like the `List` and `ForEach` SwiftUI views. + +1. Delete your list service instances and directly **pass your data to the list initializer** +2. Create your views through a content block (initializer parameter) instead of conforming your items to `View` directly (removed type erased wrapper `AnyListItem`) +3. Pass a list state binding to the initializer (before: the `ListService` managed the list state) +4. Move and delete: Instead of setting `AdvancedListActions` on your list service just pass a `onMoveAction` and/or `onDeleteAction` block to the initializer +**Before:** ```swift import AdvancedList let listService = ListService() -listService.appendItems(yourItems) -listService.listState = .items +listService.supportedListActions = .moveAndDelete(onMove: { (indexSet, index) in + // please move me +}, onDelete: { indexSet in + // please delete me +}) +listService.listState = .loading AdvancedList(listService: listService, emptyStateView: { Text("No data") @@ -136,6 +180,39 @@ AdvancedList(listService: listService, emptyStateView: { }, loadingStateView: { Text("Loading ...") }, pagination: .noPagination) + +listService.listState = .loading +// fetch your items ... +listService.appendItems(yourItems) +listService.listState = .items ``` -For more examples take a look at [AdvancedList-SwiftUI](https://github.com/crelies/AdvancedList-SwiftUI). +**After:** +```swift +import AdvancedList + +@State private var listState: ListState = .items + +AdvancedList(yourData, content: { item in + Text("Item") +}, listState: $listState, onMoveAction: { (indexSet, index) in + // move me +}, onDeleteAction: { indexSet in + // delete me +}, emptyStateView: { + Text("No data") +}, errorStateView: { error in + VStack { + Text(error.localizedDescription) + .lineLimit(nil) + + Button(action: { + // do something + }) { + Text("Retry") + } + } +}, loadingStateView: { + Text("Loading ...") +}, pagination: .noPagination) +``` From c571184aa97c0cd4748c4fcaf58c6f470434d479 Mon Sep 17 00:00:00 2001 From: Christian Elies Date: Wed, 20 Nov 2019 00:56:20 +0100 Subject: [PATCH 8/8] docs(readme): small optimizations --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f591985..7fb9b7d 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ private(set) lazy var pagination: AdvancedListPagination = { ### 📁 Move and 🗑️ delete items You can define which actions your list should support through the `onMoveAction` and `onDeleteAction` initializer parameters. -**Per default the move and delete function is disabled.** +**Per default the move and delete functions are disabled if you skip the parameters.** ```swift import AdvancedList @@ -148,9 +148,9 @@ For more examples take a look at [AdvancedList-SwiftUI](https://github.com/creli The `AdvancedList` was dramatically simplified and is now more like the `List` and `ForEach` SwiftUI views. 1. Delete your list service instances and directly **pass your data to the list initializer** -2. Create your views through a content block (initializer parameter) instead of conforming your items to `View` directly (removed type erased wrapper `AnyListItem`) -3. Pass a list state binding to the initializer (before: the `ListService` managed the list state) -4. Move and delete: Instead of setting `AdvancedListActions` on your list service just pass a `onMoveAction` and/or `onDeleteAction` block to the initializer +2. Create your views through a content block (**initializer parameter**) instead of conforming your items to `View` directly (removed type erased wrapper `AnyListItem`) +3. Pass a list state binding to the initializer (**before:** the `ListService` managed the list state) +4. **Move and delete:** Instead of setting `AdvancedListActions` on your list service just pass a `onMoveAction` and/or `onDeleteAction` block to the initializer **Before:** ```swift