本软件包提供了一个围绕 SwiftUI List view
的包装视图,增加了分页(通过我的 ListPagination 软件包)以及空、错误和加载状态,包括相应的视图。
在 Xcode 中使用其 Github 仓库 URL 添加此 Swift 软件包。(File > Swift Packages > Add Package Dependency...)
AdvancedList
视图类似于 List
和 ForEach
视图。您必须将数据 (RandomAccessCollection
) 和视图提供者 ((Data.Element) -> some View
) 传递给初始化器。除了 List
视图之外,AdvancedList
还需要一个列表状态和相应的视图。随时修改您的数据,或者如果您愿意,可以通过内容块隐藏项目。该视图会自动更新 🎉。
import AdvancedList
@State private var listState: ListState = .items
AdvancedList(yourData, content: { item in
Text("Item")
}, listState: listState, emptyStateView: {
Text("No data")
}, errorStateView: { error in
Text(error.localizedDescription)
.lineLimit(nil)
}, loadingStateView: {
Text("Loading ...")
})
从版本 6.0.0
开始,您可以使用自定义列表视图代替底层使用的 SwiftUI
List
。 例如,如果需要,您现在可以轻松使用 iOS 14 中引入的 LazyVStack。
从版本 5.0.0
升级**不会破坏任何内容**。 升级后只需添加 listView 参数
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 ...")
})
从版本 8.0.0
开始,您可以完全自由地控制在 AdvancedList
的 items
状态下呈现的内容视图。 使用 SwiftUI List
或 自定义视图
。
从版本 7.0.0
升级**不会破坏任何内容**并使用新的 API
AdvancedList(listState: yourListState, content: {
VStack {
Text("Row 1")
Text("Row 2")
Text("Row 3")
}
}, errorStateView: { error in
VStack(alignment: .leading) {
Text("Error").foregroundColor(.primary)
Text(error.localizedDescription).foregroundColor(.secondary)
}
}, loadingStateView: ProgressView.init)
Pagination
功能现在 (>= 5.0.0
) 作为 modifier
实现。 它有三种不同的状态:error
、idle
和 loading
。 如果 Pagination
的 state
发生变化,AdvancedList
会显示由指定分页对象 (AdvancedListPagination
) 的视图构建器创建的视图。 通过创建类型为 AdvancedListPaginationState
的局部状态变量 (@State
) 来跟踪当前分页状态。 在分页配置对象的 content
ViewBuilder
中使用此状态变量来确定列表中应显示哪个视图(请参见下面的示例)。
如果您想使用分页,则可以在 lastItemPagination
和 thresholdItemPagination
之间进行选择。 这两个概念都在 这里 描述。 只需在将 .pagination
修饰符添加到 AdvancedList
时指定分页的类型。
只有在列表的最后一个项目出现后,分页配置对象的 content
ViewBuilder
创建的视图才会在列表下方可见! 这样,只有在需要时才会中断用户。
示例
@State private var paginationState: AdvancedListPaginationState = .idle
AdvancedList(...)
.pagination(.init(type: .lastItem, shouldLoadNextPage: {
paginationState = .loading
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
items.append(contentsOf: moreItems)
paginationState = .idle
}
}) {
switch paginationState {
case .idle:
EmptyView()
case .loading:
if #available(iOS 14, *) {
ProgressView()
} else {
Text("Loading ...")
}
case let .error(error):
Text(error.localizedDescription)
}
})
要启用移动或删除功能,只需使用相关的 onMove
或 onDelete
视图修饰符。 如果您不添加视图修饰符,默认情况下这些功能将被禁用。
import AdvancedList
@State private var listState: ListState = .items
AdvancedList(yourData, content: { item in
Text("Item")
}, listState: listState, emptyStateView: {
Text("No data")
}, errorStateView: { error in
Text(error.localizedDescription)
.lineLimit(nil)
}, loadingStateView: {
Text("Loading ...")
})
.onMove { (indexSet, index) in
// move me
}
.onDelete { indexSet in
// delete me
}
您可以通过内容块隐藏列表中的项目。 仅当满足特定条件时才在内容块中返回视图。
以下代码显示了该视图的易用性
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 ...")
})
有关更多示例,请查看 Example
目录。
AdvancedList
得到了极大的简化,现在更像是 List
和 ForEach
SwiftUI 视图。
View
(删除类型擦除包装器 AnyListItem
)ListService
管理列表状态)AdvancedListActions
,只需将 onMoveAction
和/或 onDeleteAction
块传递给初始化器之前
import AdvancedList
let listService = ListService()
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")
}, errorStateView: { error in
VStack {
Text(error.localizedDescription)
.lineLimit(nil)
Button(action: {
// do something
}) {
Text("Retry")
}
}
}, loadingStateView: {
Text("Loading ...")
}, pagination: .noPagination)
listService.listState = .loading
// fetch your items ...
listService.appendItems(yourItems)
listService.listState = .items
之后
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)
感谢 @SpectralDragon 的提示,我可以将 onMove
和 onDelete
功能重构为视图修饰符。
之前
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)
之后
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)
.onMove { (indexSet, index) in
// move me
}
.onDelete { indexSet in
// delete me
}
Pagination
现在作为 modifier
实现 💪 最后但并非最不重要的一点是,代码文档已到达 😀
之前
private lazy var pagination: AdvancedListPagination<AnyView, AnyView> = {
.thresholdItemPagination(errorView: { error in
AnyView(
VStack {
Text(error.localizedDescription)
.lineLimit(nil)
.multilineTextAlignment(.center)
Button(action: {
// load current page again
}) {
Text("Retry")
}.padding()
}
)
}, loadingView: {
AnyView(
VStack {
Divider()
Text("Loading...")
}
)
}, offset: 25, shouldLoadNextPage: {
// load next page
}, state: .idle)
}()
@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: pagination)
之后
@State private var listState: ListState = .items
@State private var paginationState: AdvancedListPaginationState = .idle
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(.init(type: .lastItem, shouldLoadNextPage: {
paginationState = .loading
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
items.append(contentsOf: moreItems)
paginationState = .idle
}
}) {
switch paginationState {
case .idle:
EmptyView()
case .loading:
if #available(iOS 14, *) {
ProgressView()
} else {
Text("Loading ...")
}
case let .error(error):
Text(error.localizedDescription)
}
})
我替换了不必要的 listState Binding
并将其替换为简单的 value 参数。
之前
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)
之后
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)