Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,35 @@ AdvancedList(yourData, content: { item in
.lineLimit(nil)
}, loadingStateView: {
Text("Loading ...")
}, pagination: .noPagination)
})
```

### 🆕 Custom List view

Starting 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.

Upgrade from version `5.0.0` **without breaking anything**. Simply add the **listView parameter** after the upgrade:

```swift
AdvancedList(yourData, listView: { rows in
if #available(iOS 14, macOS 11, *) {
ScrollView {
LazyVStack(alignment: .leading, content: rows)
.padding()
}
} else {
List(content: rows)
}
}, content: { item in
Text("Item")
}, listState: $listState, emptyStateView: {
Text("No data")
}, errorStateView: { error in
Text(error.localizedDescription)
.lineLimit(nil)
}, loadingStateView: {
Text("Loading ...")
})
```

### 📄 Pagination
Expand Down Expand Up @@ -91,7 +119,7 @@ AdvancedList(yourData, content: { item in
.lineLimit(nil)
}, loadingStateView: {
Text("Loading ...")
}, pagination: .noPagination)
})
.onMove { (indexSet, index) in
// move me
}
Expand Down Expand Up @@ -130,7 +158,7 @@ AdvancedList(yourData, content: { item in
}
}, loadingStateView: {
Text("Loading ...")
}, pagination: .noPagination)
})
```

For more examples take a look at [AdvancedList-SwiftUI](https://github.com/crelies/AdvancedList-SwiftUI).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// AnyAdvancedListPagination.swift
//
// AdvancedList
//
// Created by Christian Elies on 31.07.20.
//
Expand Down
2 changes: 1 addition & 1 deletion Sources/AdvancedList/private/Models/ListState+error.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// ListState+error.swift
//
// AdvancedList
//
// Created by Christian Elies on 02.08.20.
//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// AdvancedListPagination.swift
//
// AdvancedList
//
// Created by Christian Elies on 15.08.19.
//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// AdvancedListPaginationState.swift
//
// AdvancedList
//
// Created by Christian Elies on 17.08.19.
//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// AdvancedListPaginationType.swift
//
// AdvancedList
//
// Created by Christian Elies on 16.08.19.
//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
//
// AnyDynamicViewContent.swift
//
// AdvancedList
//
// Created by Christian Elies on 20.11.19.
//

import SwiftUI

struct AnyDynamicViewContent: DynamicViewContent {
/// Type erased dynamic view content that generates views from an underlying collection of data.
public struct AnyDynamicViewContent: DynamicViewContent {
private let view: AnyView

private(set) var data: AnyCollection<Any>
var body: some View { view }
public private(set) var data: AnyCollection<Any>

public var body: some View { view }

init<View: DynamicViewContent>(_ view: View) {
self.view = AnyView(view)
Expand Down
59 changes: 47 additions & 12 deletions Sources/AdvancedList/public/Views/AdvancedList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ import SwiftUI
/// An `advanced` container that presents rows of data arranged in a single column.
/// Built-in `empty`, `error` and `loading` state.
/// Supports `lastItem` or `thresholdItem` pagination.
public struct AdvancedList<Data: RandomAccessCollection, Content: View, EmptyStateView: View, ErrorStateView: View, LoadingStateView: View> : View where Data.Element: Identifiable {
public struct AdvancedList<Data: RandomAccessCollection, ListView: View, Content: View, EmptyStateView: View, ErrorStateView: View, LoadingStateView: View> : View where Data.Element: Identifiable {
public typealias Rows = () -> AnyDynamicViewContent
public typealias OnMoveAction = Optional<(IndexSet, Int) -> Void>
public typealias OnDeleteAction = Optional<(IndexSet) -> Void>

private typealias Configuration = (AnyDynamicViewContent) -> AnyDynamicViewContent

private var pagination: AnyAdvancedListPagination?
private var data: Data
private var listView: ((Rows) -> ListView)?
private var content: (Data.Element) -> Content
private var listState: Binding<ListState>
private let emptyStateView: () -> EmptyStateView
Expand All @@ -33,6 +35,30 @@ public struct AdvancedList<Data: RandomAccessCollection, Content: View, EmptySta
///
/// - Parameters:
/// - data: The data for populating the list.
/// - listView: A view builder that creates a custom list view from the given type erased dynamic view content representing the rows of the list.
/// - content: A view builder that creates the view for a single row of the list.
/// - listState: A binding to a property that determines the state of the list.
/// - emptyStateView: A view builder that creates the view for the empty state of the list.
/// - errorStateView: A view builder that creates the view for the error state of the list.
/// - loadingStateView: A view builder that creates the view for the loading state of the list.
public init(_ data: Data, @ViewBuilder listView: @escaping (Rows) -> ListView, @ViewBuilder content: @escaping (Data.Element) -> Content, listState: Binding<ListState>, @ViewBuilder emptyStateView: @escaping () -> EmptyStateView, @ViewBuilder errorStateView: @escaping (Error) -> ErrorStateView, @ViewBuilder loadingStateView: @escaping () -> LoadingStateView) {
self.data = data
self.listView = listView
self.content = content
self.listState = listState
self.emptyStateView = emptyStateView
self.errorStateView = errorStateView
self.loadingStateView = loadingStateView
configurations = []
}
}

extension AdvancedList where ListView == List<Never, AnyDynamicViewContent> {
/// Initializes the list with the given values.
/// Uses the native `SwiftUI` `List` as list view.
///
/// - Parameters:
/// - data: The data for populating the list.
/// - content: A view builder that creates the view for a single row of the list.
/// - listState: A binding to a property that determines the state of the list.
/// - emptyStateView: A view builder that creates the view for the empty state of the list.
Expand All @@ -46,6 +72,7 @@ public struct AdvancedList<Data: RandomAccessCollection, Content: View, EmptySta
self.errorStateView = errorStateView
self.loadingStateView = loadingStateView
configurations = []
listView = getListView
}
}

Expand All @@ -55,7 +82,9 @@ extension AdvancedList {
case .items:
if !data.isEmpty {
VStack {
getListView()
if let listView = listView {
listView(rows)
}

if let pagination = pagination, isLastItem {
pagination.content()
Expand Down Expand Up @@ -104,23 +133,29 @@ extension AdvancedList {
}

// MARK: - Private helper
extension AdvancedList {
private extension AdvancedList {
private func configure(_ configuration: @escaping Configuration) -> Self {
var result = self
result.configurations.append(configuration)
return result
}

private func getListView() -> some View {
List {
configurations
.reduce(AnyDynamicViewContent(ForEach(data) { item in
getItemView(item)
})) { (currentView, configuration) in configuration(currentView) }
}
func getListView(rows: Rows) -> List<Never, AnyDynamicViewContent> {
List(content: rows)
}

func rows() -> AnyDynamicViewContent {
configurations
.reduce(
AnyDynamicViewContent(
ForEach(data) { item in
getItemView(item)
}
)
) { (currentView, configuration) in configuration(currentView) }
}

private func getItemView(_ item: Data.Element) -> some View {
func getItemView(_ item: Data.Element) -> some View {
content(item)
.onAppear {
listItemAppears(item)
Expand All @@ -131,7 +166,7 @@ extension AdvancedList {
}
}

private func listItemAppears(_ item: Data.Element) {
func listItemAppears(_ item: Data.Element) {
guard let pagination = pagination else {
return
}
Expand Down