SwiftUI 高级List分页与无限滚动之高级版(2020教程
2020-01-26 本文已影响0人
iCloudEnd
在《SwiftUI 高级List分页与无限滚动之基础版(2020教程)》中我们介绍了List分页的基础实现方法,但是这种方法没有站在用户的角度思考,下面我们以用户为中心制作分页。
下面,我们将研究一下如何通过阈值控制获取下一页时间。
RandomAccessCollection+isThresholdItem
这次,我们将从extentions的RandomAccessCollection开始。这次,我们将实现一个名为isThresholdItem的函数,该函数确定给定item是否为阈值item。
extension RandomAccessCollection where Self.Element: Identifiable {
func isThresholdItem<Item: Identifiable>(offset: Int,
item: Item) -> Bool {
guard !isEmpty else {
return false
}
guard let itemIndex = firstIndex(where: { AnyHashable($0.id) == AnyHashable(item.id) }) else {
return false
}
let distance = self.distance(from: itemIndex, to: endIndex)
let offset = offset < count ? offset : count - 1
return offset == (distance - 1)
}
}
此函数查找给定项目的索引。如果找到,它将计算到终点索引的距离。指定的偏移量(即,结束之前的项目数)应等于distance —1。我们必须从该距离中减去1,因为结束索引等于count属性的值(即,集合中的当前项目数) )。我还为偏移量添加了一个简单的验证检查。偏移量应小于集合中的当前项目数.
界面部分
UI实现与第一种方法中的UI几乎相同。但是,有一个关键的区别,那就是listItemAppears函数
请记住,这里我们从第一种方法重用了isLastItem函数。仅当用户到达列表的末尾并且对下一页的请求仍在进行中时,才会显示加载视图
struct ListPaginationThresholdExampleView: View {
@State private var items: [String] = Array(0...24).map { "Item \($0)" }
@State private var isLoading: Bool = false
@State private var page: Int = 0
private let pageSize: Int = 25
private let offset: Int = 10
var body: some View {
NavigationView {
List(items) { item in
VStack(alignment: .leading) {
Text(item)
if self.isLoading && self.items.isLastItem(item) {
Divider()
Text("Loading ...")
.padding(.vertical)
}
}.onAppear {
self.listItemAppears(item)
}
}
.navigationBarTitle("List of items")
.navigationBarItems(trailing: Text("Page index: \(page)"))
}
}
}
代替调用isLastItem,我们调用isThresholdItem来检查给定项目是否为阈值项目。
extension ListPaginationThresholdExampleView {
private func listItemAppears<Item: Identifiable>(_ item: Item) {
if items.isThresholdItem(offset: offset,
item: item) {
isLoading = true
/*
Simulated async behaviour:
Creates items for the next page and
appends them to the list after a short delay
*/
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
self.page += 1
let moreItems = self.getMoreItems(forPage: self.page, pageSize: self.pageSize)
self.items.append(contentsOf: moreItems)
self.isLoading = false
}
}
}
}
如果您是一位特别专心的读者,您可能会注意到缺少一些代码。让我们进入下一部分。
载入更多的数据
extension ListPaginationExampleView {
/*
In a real app you would probably fetch data
from an external API.
*/
private func getMoreItems(forPage page: Int,
pageSize: Int) -> [String] {
let maximum = ((page * pageSize) + pageSize) - 1
let moreItems: [String] = Array(items.count...maximum).map { "Item \($0)" }
return moreItems
}
}
String 实现Identifiable
/*
If you want to display an array of strings
in the List view you have to specify a key path,
so each string can be uniquely identified.
With this extension you don't have to do that.
*/
extension String: Identifiable {
public var id: String {
return self
}
}
好的,两种方法都给大家介绍了。我先去睡个午觉,下午把整个项目放到github上
项目完整代码
//
// data.swift
// Swift_pagination_01
//
// Created by cf on 2020/1/26.
// Copyright © 2020 cf. All rights reserved.
//
import Foundation
import SwiftUI
struct DemoItem: Identifiable {
let id = UUID()
var sIndex = 0
var page = 0
}
extension RandomAccessCollection where Self.Element: Identifiable {
func isLastItem<Item: Identifiable>(_ item: Item) -> Bool {
guard !isEmpty else {
return false
}
guard let itemIndex = firstIndex(where: { $0.id.hashValue == item.id.hashValue }) else {
return false
}
let distance = self.distance(from: itemIndex, to: endIndex)
return distance == 1
}
func isThresholdItem<Item: Identifiable>(offset: Int,
item: Item) -> Bool {
guard !isEmpty else {
return false
}
guard let itemIndex = firstIndex(where: { AnyHashable($0.id) == AnyHashable(item.id) }) else {
return false
}
let distance = self.distance(from: itemIndex, to: endIndex)
let offset = offset < count ? offset : count - 1
return offset == (distance - 1)
}
}
//
// ContentView.swift
// Swift_pagination_01
//
// Created by cf on 2020/1/26.
// Copyright © 2020 cf. All rights reserved.
//
import SwiftUI
struct ContentView: View {
@State private var items: [DemoItem] = Array(0...24).map { DemoItem(sIndex: $0,page:0) }
@State private var isLoading: Bool = false
@State private var page: Int = 0
private let pageSize: Int = 25
private let offset: Int = 10
var body: some View {
NavigationView {
List(items) { item in
VStack {
Text("page:\(item.page) item:\(item.sIndex)")
if self.isLoading && self.items.isLastItem(item) {
Divider()
Text("Loading ...")
.padding(.vertical)
}
}.onAppear {
self.listItemAppears(item)
}
}
.navigationBarTitle("List of items")
.navigationBarItems(trailing: Text("Page index: \(page)"))
}
}
}
extension ContentView {
private func listItemAppears<Item: Identifiable>(_ item: Item) {
if items.isThresholdItem(offset: offset,item: item) {
isLoading = true
/*
Simulated async behaviour:
Creates items for the next page and
appends them to the list after a short delay
*/
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
self.page += 1
let moreItems = self.getMoreItems(forPage: self.page, pageSize: self.pageSize)
self.items.append(contentsOf: moreItems)
self.isLoading = false
}
}
}
/*
func getMoreItems(forPage: Int, pageSize: Int) -> [DemoItem]{
let sitems: [DemoItem] = Array(0...24).map { DemoItem(sIndex: $0,page:forPage) }
return sitems
}
*/
private func getMoreItems(forPage page: Int,
pageSize: Int) -> [DemoItem] {
let maximum = ((page * pageSize) + pageSize) - 1
let moreItems: [DemoItem] = Array(items.count...maximum).map { DemoItem(sIndex: $0,page:page) }
return moreItems
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
项目下载地址
更多SwiftUI教程和代码关注专栏
- 请关注我的专栏 SwiftUI教程与源码