【问题标题】:SwiftUI pagination for List objectList 对象的 SwiftUI 分页
【发布时间】:2020-05-09 03:00:12
【问题描述】:

我在 SwiftUI 中实现了一个带有搜索栏的列表。现在我想为这个列表实现分页。当用户滚动到列表底部时,应该加载新元素。我的问题是,我怎样才能检测到用户滚动到最后?发生这种情况时,我想加载新元素,附加它们并将它们显示给用户。

我的代码如下所示:

import Foundation
import SwiftUI

struct MyList: View {
    @EnvironmentObject var webService: GetRequestsWebService

    @ObservedObject var viewModelMyList: MyListViewModel

    @State private var query = ""

    var body: some View {

        let binding = Binding<String>(
            get: { self.query },
            set: { self.query = $0; self.textFieldChanged($0) }
        )

        return NavigationView {
            // how to detect here when end of the list is reached by scrolling?
            List {
                // searchbar here inside the list element
                TextField("Search...", text: binding) {
                    self.fetchResults()
                }

                ForEach(viewModelMyList.items, id: \.id) { item in
                    MyRow(itemToProcess: item)
                }
            } 
            .navigationBarTitle("Title")
        }.onAppear(perform: fetchResults)

    }

    private func textFieldChanged(_ text: String) {        
        text.isEmpty ? viewModelMyList.fetchResultsThrottelt(for: nil) : viewModelMyList.fetchResultsThrottelt(for: text)
    }

    private func fetchResults() {
        query.isEmpty ? viewModelMyList.fetchResults(for: nil) : viewModelMyList.fetchResults(for: query)
    }
}

这种情况也有点特殊,因为列表中包含搜索栏。我会感谢任何建议,因为有了这个:)。

【问题讨论】:

    标签: ios swift list swiftui combine


    【解决方案1】:

    由于您已经有一个 List 并带有一个用于搜索栏的人工行,您可以简单地将另一个视图添加到列表中,当它出现在屏幕上时将触发另一个提取(使用 Josh 建议的 onAppear())。通过这样做,您不必进行任何“复杂”的计算来知道一行是否是最后一行……人工行总是最后一行!

    我已经在我的一个项目中使用了它,但我从未在屏幕上看到过这个元素,因为加载在它出现在屏幕上之前就被触发得如此之快。 (您当然可以使用透明/不可见元素,甚至可以使用微调器;-))

                List {
                    TextField("Search...", text: binding) { /* ... */ }
    
                    ForEach(viewModelMyList.items, id: \.id) { item in
                        // ...
                    }
    
                    if self.viewModelMyList.hasMoreRows {
                        Text("Fetching more...")
                            .onAppear(perform: {
                                self.viewModelMyList.fetchMore()
                            })
                    }
    

    【讨论】:

    • 太好了:+1,谢谢 pd95!这也是您在此处提出的一个非常好的解决方案。当这样做时,也可以向用户显示一个小信息。正在那里进行,数据正在加载。非常感谢。
    【解决方案2】:

    .onAppear() 添加到MyRow 并让它使用刚刚出现的项目调用viewModel。然后,您可以检查它是否等于列表中的最后一项,或者它的 n 项是否与列表末尾相距并触发您的分页。

    【讨论】:

      【解决方案3】:

      这个对我有用:

      您可以使用两种不同的方法向列表添加分页:最后一项方法和阈值项方法。

      这就是这个包向 RandomAccessCollection 添加两个函数的方式:

      isLastItem

      使用此函数检查当前列表项迭代中的项是否是您集合的最后一项。

      isThresholdItem

      使用此功能,您可以确定当前列表项迭代的项是否是您定义的阈值的项。将偏移量(到最后一项的距离)传递给函数,以便确定阈值项。

      import SwiftUI
      
      extension RandomAccessCollection where Self.Element: Identifiable {
          public func isLastItem<Item: Identifiable>(_ item: Item) -> Bool {
              guard !isEmpty else {
                  return false
              }
              
              guard let itemIndex = lastIndex(where: { AnyHashable($0.id) == AnyHashable(item.id) }) else {
                  return false
              }
              
              let distance = self.distance(from: itemIndex, to: endIndex)
              return distance == 1
          }
          
          public func isThresholdItem<Item: Identifiable>(
              offset: Int,
              item: Item
          ) -> Bool {
              guard !isEmpty else {
                  return false
              }
              
              guard let itemIndex = lastIndex(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)
          }
      }
      

      示例

      最后一项方法:

      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)"))
              }
          }
      }
      
      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
                  }
              }
          }
      }
      

      阈值项方法:

      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)"))
              }
          }
      }
      
      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
                  }
              }
          }
      }
      

      字符串扩展:

      /*
          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 anymore.
       */
      extension String: Identifiable {
          public var id: String {
              return self
          }
      }
      

      Christian Elies, code reference

      【讨论】:

        猜你喜欢
        • 2020-05-01
        • 2018-01-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-05
        • 1970-01-01
        相关资源
        最近更新 更多