SwiftUI 高級List分頁與無限滾動之基礎版(2020教程)
盡管我們可以訪問List中的具體item,但是我們不知道List滾動到了當前哪個位置,也不知道我們到List末尾的距離。這些數據都是我們進行分頁到基礎。
Pagination(分頁)對于每個人都有不同的含義,因此我們先給我分頁的目標做個明確定義:
在滾動過程中,List應提取并追加下一頁的數據。當用戶到達列表末尾且請求仍在進行中時,應顯示加載視圖。
基于上面的定義,讓我們實現一個解決方案來解決這些問題和給List增加分頁功能
實現
在此節中,我們將介紹兩種不同的方案。第一種將更為簡單,第二種將更為高級用戶喜歡。
第一種方法
最簡單的方法就是監測當前item是否是最后一個。如果是,我們則觸發一個異步請求去提取下一頁的數據。
RandomAccessCollection+isLastItem
由于List支持RandomAccessCollection,我們可以創建一個extension并實現isLastItem 函數。Self關鍵詞是必須的,它將限制extension的元素必須實現Identifable。
好了,上面這段文字沒有深入研究過swift的朋友肯定要懵圈了。大家可以參考我之前文章,簡單了解一下RandomAccessCollection 和Identifiable
- SwiftUI RandomAccessCollection 是什么如何用(2020)
- SwiftUI 基礎之06 Identifiable 有什么用
- SwiftUI guard 是什么如何用(2020教程)
下面是代碼
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
}
}
上面代碼用于判斷item是否為List的末尾。
該函數在集合中查找給定項目的索引。它使用id屬性的哈希值(需要實現Identifiable協議)將其與列表中的其他項目進行比較。如果找到了項目索引,則意味著項目索引與結束索引之間的距離必須恰好為一(結束索引等于集合中當前項目的數量)。這樣我們才能知道給定的項目是最后一個項目
為了代替hash值的比較,我們可以使用 type-erased wrapper AnyHashable來直接比較Hashable類型。
guard let itemIndex = firstIndex(where: { AnyHashable($0.id) == AnyHashable(item.id) }) else {
return false
}
好了,基礎的業務邏輯我們已經實現,下面我們來實現界面部分。
界面
如果滾動到List底部,我們可以一個List 更新事件。為了達到這個目標,我們可以在根視圖新增一個onAppear修飾器(在例子中,我們根視圖是VStack)。onAppear將隨后調用listItemAppears函數。
如果當前遍歷item是最后一個,一個等待視圖將顯示給用戶。在例子中,我們就用簡單的Text("Loading...")。
由于SwiftUI是聲明式的,因此下面的代碼不言自明,非常易讀:
struct ListPaginationExampleView: 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
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)"))
}
}
}
輔助函數listItemAppears內部檢查給定的item是否為最后一個。如果是最后一項,則當前頁面會增加,下一頁的項目會添加到列表中。此外,我們通過isLoading變量跟蹤加載狀態,該變量定義何時顯示加載視圖。
extension ListPaginationExampleView {
private func listItemAppears<Item: Identifiable>(_ item: Item) {
if items.isLastItem(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() + 3) {
self.page += 1
let moreItems = self.getMoreItems(forPage: self.page, pageSize: self.pageSize)
self.items.append(contentsOf: moreItems)
self.isLoading = false
}
}
}
}
通過上面的代碼,當前迭代中的項目是最后一個項目時,我們才獲取項目的下一頁。
完整代碼
創建個data.swift用于處理數據問題
//
// 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
}
}
界面部分
//
// 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
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.isLastItem(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() + 1) {
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
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
最終效果
項目完成代碼
https://github.com/zhishidapang/SwiftUI-List-Pagination
下一步工作
但這并不是真正的最佳用戶體驗,對吧?在實際應用中,如果要達到或超過定義的閾值,我們希望預加載下一頁。此外,我們僅應在確實有必要時(即,如果請求花費的時間比預期的長),使用加載指示器中斷用戶。我認為,這將帶來更好的用戶體驗。
考慮到這些用戶體驗的問題,讓我們跳到第二種方法。
更多SwiftUI教程和代碼關注專欄
- 請關注我的專欄 SwiftUI教程與源碼
智能推薦
SwiftUI 基礎之02 NavigationView和List(2020)
SwiftUI 基礎之NavigationView和List(2020) 效果 [email protected] 更多SwiftUI教程和代碼關注專欄 請關注我的專欄 SwiftUI教程與源碼...
SwiftUI 基礎之List如何設置風格
UIKit時代我們可以給UITableview設置不同顯示風格,SwiftUI世界里該如何實現呢? 實現方法非常簡單,請看效果和代碼 效果 [email protected] 代碼 參考文章 https://stackoverflow.com/questions/56498045/remove-extra-separators-below-list-in-swiftui 更多S...
iOS SwiftUI macOS之List基礎使用
本文價值與收獲 看完本文后,您將能夠作出下面的界面 image.png 看完本文您將掌握的技能 macOS中list基礎使用 代碼 技術交流 QQ:3365059189 SwiftUI技術交流QQ群:518696470 請關注我的專欄icloudend, SwiftUI教程與源碼 https://www.jianshu.com/c/7b3e3b671970...
SwiftUI 復用舊項目代碼之UIViewcontroller(2020版教程)
SwiftUI 復用舊項目代碼之UIViewcontroller(2020版教程) SwiftUI 非常好用,開發界面高效快捷,但是具體到業務層面,復用歷史代碼是必須要研究到事情。 如何在SwiftUI中調用UIViewcontroller 1、 創建一個 Wrapper 例如我們有個Example1ViewController舊代碼,我們可以先通過UIViewControllerRepresen...
SwiftUI 控件之Button 按鈕初級和高級實用(2020版教程)
SwiftUI 控件之Button 按鈕初級和高級實用(2020版教程) Button是一個非常基本的UI控件,您可以在所有應用中找到該控件。如果您以前學習過iOS編程,則SwiftUI中的Button與UIKit中的UIButton非常相似。它更加靈活和可定制。本文將給您介紹一下幾個方面的內容: 如何創建一個簡單的按鈕并處理用戶的選擇 如何自定義按鈕的背景,填充和字體 如何在按鈕上添加邊框 如何...
猜你喜歡
SwiftUI List 如何顯示dictionary
本文價值與收獲 看完本文后,您將能夠作出下面的界面 看完本文您將掌握的技能 list基礎使用 顯示dictionary 代碼 技術交流 QQ:3365059189 SwiftUI技術交流QQ群:518696470...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...