【问题标题】:Swift tableView PaginationSwift tableView 分页
【发布时间】:2016-05-23 21:23:57
【问题描述】:

我已经成功地使用 json 解析代码工作了 tableview。但是可能还有 1000 多个项目,所以在滚动底部时需要分页。我不知道如何在下面的代码中执行此操作。对于objective-c 有很多示例,但对于swift 我没有找到工作示例。我在等你的帮助。我想会帮助太多人。谢谢 !

import UIKit

class ViewController: UIViewController, UITableViewDataSource,UITableViewDelegate {

    let kSuccessTitle = "Congratulations"
    let kErrorTitle = "Connection error"
    let kNoticeTitle = "Notice"
    let kWarningTitle = "Warning"
    let kInfoTitle = "Info"
    let kSubtitle = "You've just displayed this awesome Pop Up View"


    @IBOutlet weak var myTableView: UITableView!
    @IBOutlet weak var myActivityIndicator: UIActivityIndicatorView!

    var privateList = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)

        loadItems()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        return privateList.count
    }




    internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
    {

       let cell:myCell = tableView.dequeueReusableCellWithIdentifier("myCell") as! myCell

        cell.titleLabel.text = privateList[indexPath.row]


        return cell
    }


    func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

        if (editingStyle == UITableViewCellEditingStyle.Delete){

         print(indexPath.row)


            let alert = SCLAlertView()
            alert.addButton("Hayır"){ }
            alert.addButton("Evet") {

                self.myTableView.beginUpdates()

                 self.privateList.removeAtIndex(indexPath.row)
                tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Left)
                print("Silindi")

                self.myTableView.endUpdates()

                  self.loadItems()

            }
            alert.showSuccess(kSuccessTitle, subTitle: kSubtitle)

        }


    }





    func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // the cells you would like the actions to appear needs to be editable
        return true
    }



    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {


        if(segue.identifier == "Detail") {

            let destinationView = segue.destinationViewController as! DetailViewController

            if let indexPath = myTableView.indexPathForCell(sender as! UITableViewCell) {

                destinationView.privateLista = privateList[indexPath.row]

            }
        }
    }



    internal func tableView(tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat
    {
        return 0.0
    }


    func loadItems()
    {
     loadItemsNow("privateList")

    }

    func loadItemsNow(listType:String){
        myActivityIndicator.startAnimating()
        let listUrlString =  "http://bla.com/json2.php?listType=" + listType + "&t=" + NSUUID().UUIDString
        let myUrl = NSURL(string: listUrlString);
        let request = NSMutableURLRequest(URL:myUrl!);
        request.HTTPMethod = "GET";

        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
            data, response, error in

            if error != nil {
                print(error!.localizedDescription)
                dispatch_async(dispatch_get_main_queue(),{
                    self.myActivityIndicator.stopAnimating()
                })

                return
            }


            do {

                let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSArray

                if let parseJSON = json {


                        self.privateList = parseJSON as! [String]

                }

            } catch {
                print(error)

            }

            dispatch_async(dispatch_get_main_queue(),{
                self.myActivityIndicator.stopAnimating()
                self.myTableView.reloadData()
            })


        }

        task.resume()
    }


}

【问题讨论】:

  • 您是否一次获取数千个对象?
  • @ShehzadAli 不,但在滚动底部时需要刷新新的会很好。还有很多项目会花费很多时间。

标签: ios swift uitableview pagination


【解决方案1】:

另一种方法是:您可以在每次发送请求时设置获取元素的阈值:

假设您是第一次获取 20 个元素。您将保存最后获取的记录 ID 或编号,以获取接下来 20 个元素的列表。

let lastFetchedIndex = 20;

我假设您已经在 myArray.xml 中添加了这些记录。 MyArray 是 tableView 的数据源。现在 myArray 包含 40 个对象。我要列出现在需要在 tableView 中插入的行的 indexPaths 列表。

var indexPathsArray = [NSIndexPath]()


for index in lastFetchedIndex..<myArray.count{
    let indexPath = NSIndexPath(forRow: index, inSection: 0)
    indexPathsArray.append(indexPath)

}

在这里我正在更新我的 tableView。确保您的数据源我的意思是您的 myArray 已经更新。以便它可以正确插入行。

self.tableView.beginUpdates()
tableView!.insertRowsAtIndexPaths(indexPathsArray, withRowAnimation: .Fade)
self.tableView.endUpdates()

【讨论】:

  • 感谢老兄的回答,但我在哪里可以添加代码?
  • 我已经分步说明了。在获得大量新对象后,使用此代码更新您的代码。将来自服务器的项目块添加到用作 tableview 数据源的数组中。然后在您的代码中添加上述代码。上面的代码可以放在一个方法中。
  • 请编辑您的答案并过去我的原始代码并使用您的分页代码进行编辑,稍后我会检查,如果有效,我会批准您的答案..
【解决方案2】:

为此,您还需要更改服务器端。

  1. 服务器将接受API url 中的fromIndexbatchSize 作为查询参数。

    let listUrlString =  "http://bla.com/json2.php?listType=" + listType + "&t=" + NSUUID().UUIDString + "&batchSize=" + batchSize + "&fromIndex=" + fromIndex
    
  2. 在服务器响应中,会有一个额外的密钥totalItems。这将用于识别是否收到所有物品。数组或项目 fromIndexbatchSize 的项目数。

在应用端

  1. 首先将使用fromIndex = 0batchSize = 20 调用loadItem()(例如在viewDidLoad()viewWillAppear 中)。在第一次调用 loadItem() 之前从 privateList 数组中删除所有项目

  2. 服务器返回一个包含前 20 个项目的数组和totalItems 服务器中的项目总数。

  3. 追加privateList数组中的20个项目并重新加载tableView

  4. tableView:cellForRowAtIndexPath 方法中检查单元格是否是最后一个单元格。并检查totalItems(表单服务器)是否大于privateList.count。这意味着服务器中有更多的项目要加载

    if indexPath.row == privateList.count - 1 { // last cell
        if totalItems > privateList.count { // more items to fetch
            loadItem() // increment `fromIndex` by 20 before server call
        }
    }
    

问题: where is refresh ? will be scrolling ?

收到服务器响应后,在数组中添加新项目后刷新。 (第 3 步)

当用户滚动时,每个单元格都会触发tableView:cellForRowAtIndexPath。代码正在检查它是否是最后一个单元格并获取剩余的项目。 (第 4 步)

已添加示例项目:
https://github.com/rishi420/TableViewPaging

【讨论】:

  • 刷新在哪里?会滚动吗?
  • 你能写完整代码集成完整代码我将测试
  • 你能写完整代码集成完整代码我将测试
  • @SwiftDeveloper 答案更新为示例项目链接。下载、运行并查看print 日志。
  • :) 给我一个示例 url http://bla.com/json2.php?listType=... 我可以点击和测试
【解决方案3】:

我在一个项目中需要类似的东西,我的解决方案是:

1 - 创建一个变量 numberOfObjectsInSubArray(初始值 30 或任何你想要的)

2 - 每次点击“显示更多”时,创建一个子数组以从您的 privateList 数组中添加一些对象

    let subArray = privateList?.subarrayWithRange(NSMakeRange(0, numberOfObjectsInSubArray))

然后用它来

internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    return subArray.count
}

3- 每当您需要显示更多对象时,请执行以下操作:

func addMoreObjectsOnTableView () {

    numberOfObjectsInSubArray += 30

    if (numberOfObjectsInSubArray < privateList.count) {

        subArray = privateList?.subarrayWithRange(NSMakeRange(0, numberOfObjectsInSubArray))  

    } else {

        subArray = privateList?.subarrayWithRange(NSMakeRange(0, privateList.count))  
    }

    tableView.reloadData()
}

希望对你有帮助

【讨论】:

    【解决方案4】:

    在您的表格视图中添加另一个部分,让该部分只有 1 行,这将是一个包含活动指示器的单元格,以表示正在加载。

    internal func numberOfSectionsInTableView(tableView: UITableView) -> Int
    {
        return 2;
    }
    
    internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
        {
            if section == 0 {
                return privateList.count
            } else if section == 1 {    // this is going to be the last section with just 1 cell which will show the loading indicator
                return 1
            }
        }
    
    internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
    {
       if section == 0 {
           let cell:myCell = tableView.dequeueReusableCellWithIdentifier("myCell") as! myCell
    
            cell.titleLabel.text = privateList[indexPath.row]
    
    
            return cell
        } else if section == 1 { 
            //create the cell to show loading indicator
            ...
    
            //here we call loadItems so that there is an indication that something is loading and once loaded we relaod the tableview
            self.loadItems()
        }
    }
    

    【讨论】:

    • 其实我以为这个方法行得通,可惜不行,Number of rows抛出了错误,需要在If bock之外添加return语句,CellforRow这个错误抱怨缺少return Cell外If 块,修复了这个问题,但即使是艰难的第 1 节也足够可见并且在桌子的下方,它仍然会触发加载项......
    【解决方案5】:

    在 tableview 中使用scrollviewDelegate 是一种很好且有效的方法 只需在您的viewController 中添加UIScrollViewDelegate 在视图控制器中

    //For Pagination
    var isDataLoading:Bool=false
    var pageNo:Int=0
    var limit:Int=20
    var offset:Int=0 //pageNo*limit
    var didEndReached:Bool=false
    viewDidLoad(_){
    tableview.delegate=self //To enable scrollviewdelegate
    }
    

    覆盖此委托的两个方法

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    
            print("scrollViewWillBeginDragging")
            isDataLoading = false
        }
    
    
    
        func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
            print("scrollViewDidEndDecelerating")
        }
        //Pagination
        func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    
                print("scrollViewDidEndDragging")
                if ((tableView.contentOffset.y + tableView.frame.size.height) >= tableView.contentSize.height)
                {
                    if !isDataLoading{
                        isDataLoading = true
                        self.pageNo=self.pageNo+1
                        self.limit=self.limit+10
                        self.offset=self.limit * self.pageNo
                        loadCallLogData(offset: self.offset, limit: self.limit)
    
                    }
                }
    
    
        }
    

    【讨论】:

    • 偏移的目的是什么?,self.limit是我假设的pageSize。
    【解决方案6】:

    这里是集合视图的示例代码:

    var page = 0
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
        print("page Num:\(page)")
    }
    
    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath){
         if arrImagesData.count-1 == indexPath.row && arrImagesData.count%10 == 0{
            getMoreImages(page)
         }
    }
    
    func getMoreImages(page:Int){ 
       //hit api
       if api_success == true {
           if self.page == 0 {
              self.arrImagesData.removeAll()
           }
       self.arrImagesData.appendContentsOf(api_data)
       self.collectionImages.reloadData()
       self.page = self.page + 1
       }
    }
    

    【讨论】:

      【解决方案7】:

      SWIFT 3.0 和 4.0

      如果您在 API 请求中发送页码,那么这是在您的应用中实现分页的理想方式。

      1. 用初始值为 0 和 bool 声明变量 current Page 以检查是否正在加载任何初始值为 false 的列表
          var currentPage : Int = 0
          var isLoadingList : Bool = false
      
      1. 这是获取列表示例的函数:
          func getListFromServer(_ pageNumber: Int){
              self.isLoadingList = false
              self.table.reloadData()
          }
      
      1. 这是增加页码并调用 API 函数的函数
         func loadMoreItemsForList(){
             currentPage += 1
             getListFromServer(currentPage)
         }
         
      
      1. 这是scrollView滚动时调用的方法
          func scrollViewDidScroll(_ scrollView: UIScrollView) {
              if (((scrollView.contentOffset.y + scrollView.frame.size.height) > scrollView.contentSize.height ) && !isLoadingList){
                  self.isLoadingList = true
                  self.loadMoreItemsForList()
              }
          }
      

      附: bool isLoadingList 的作用是防止滚动视图一拖就拉到更多列表到表格视图的底部。

      【讨论】:

      • reloadData() 是否会让用户滚动到顶部?
      • no reloadData() 不会滚动到顶部,使用tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top , animated: true) 滚动到顶部
      • 我已经完美地实现了您的功能,但是当我滚动得更快或页面计数到 4 或 5 不包含数据时,它给了我index out of bound 错误。如何持续显示数据?
      【解决方案8】:

      现在在 iOS10 中添加了一个新协议,这变得更容易了:UITableViewDataSourcePrefetching

      https://developer.apple.com/documentation/uikit/uitableviewdatasourceprefetching

      【讨论】:

      【解决方案9】:

      我尝试了一种使用 willDisplayCell 的方法。但它会在滚动过程中产生不必要的停止,从而使用户体验不佳。 我认为更好的方法是在 scrollViewDidEndDecelerating 委托方法中做到这一点。它在滚动完成时调用,然后才出现新数据。用户看到有新内容,如果他愿意,可以再次滚动。我已经接受了here 的答案,但我使用的是 scrollViewDidEndDecelerating 而不是 scrollViewDidEndDragging。就我而言,它看起来更好。这是我项目中的一些代码。

      func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
          guard scrollView == tableView,
              (scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height,
              !viewModel.isLastPeriodicsPage else { return }
      
          viewModel.paginatePeriodics(tableView.getLastIndexPath())
      }
      

      【讨论】:

      • 你能解释一下这个例子中 viewModel.paginatePeriodics() 是什么吗?
      • 这个方法调用我们的服务器 API 来获取新部分的项目,从给定的索引开始,没什么特别的
      • 此方法存在问题。这取决于用户滚动页面。当屏幕上有空间显示更多项目时,此方法会失败,但它会等待用户滚动触发 API 调用以进行下一页加载。
      【解决方案10】:

      制作了一个通用的分页框架:?

      https://github.com/eonist/PaginationTable

      let table = Table(rowData: [], frame: .zero, style: .plain)
        view = table
        table.isFetching = true
        Table.fetchData(range: table.paginationRange) { rowItem in
           DispatchQueue.main.async { [weak table] in
              table?.rowData += rowItem
              table?.reloadData()
              table?.paginationIndex += Table.paginationAmount // set the new pagination index
              table?.isFetching = false
           }
        }
      

      【讨论】:

        【解决方案11】:

        通过UITableViewDelegate,你可以调用函数

           func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            let lastItem = self.mes.count - 1
            if indexPath.row == lastItem {
                print("IndexRow\(indexPath.row)")
                if currentPage < totalPage {
                    currentPage += 1
                   //Get data from Server
                }
            }
        }
        

        【讨论】:

          【解决方案12】:
          //It works fine 
          func getPageCount(TotalCount : Int) -> Int{
              var num = TotalCount
              let reminder = num % 50
              print(reminder)
              if reminder != 0{
                  num = TotalCount/50
                  num = num + 1
          
              }else{
                  num = TotalCount/50
              }
              return num
          }
          
          func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
              let TotalPage =  self.getPageCount(TotalCount: Int(Datacount)!)
              let lastItem = self.mainArr.count - 1
              if indexPath.row == lastItem {
                  print("IndexRow\(indexPath.row)")
                  if self.page < TotalPage-1 {
                      self.view_Loader.isHidden = false
                      self.view_LoaderHeight.constant = 50
                  self.page += 1
                  self.YourAPI()
                  }
              }
          }`
          

          【讨论】:

            【解决方案13】:

            API 处理程序是用于网络调用的 api 处理程序,只进行 POST 和 GET 调用。 getNotifications 基本上只是一个带有参数(偏移量和 pageSize)的后调用,并且作为响应有列表。 主要逻辑是根据 willDisplay collectionView 委托中的单元格更改偏移量。如果您有任何问题,请发表评论,很乐意提供帮助。

            var isFetching: Bool = false
            var offset = 0
            var totalListOnServerCount = 20 // it must be returned from server
            var pageSize = 10 // get 10 objects for instance
            // MARK: - API Handler
            private func fetchNotifications(){
                // return from function if already fetching list
                guard !isFetching else {return}
                    if offset == 0{
                        // empty list for first call i.e offset = 0
                        self.anyList.removeAll()
                        self.collectionView.reloadData()
                    }
                    isFetching = true
                    // API call to fetch notifications with given offset or page number depends on server logic just simple POST Call
                    APIHandler.shared.getNotifications(offset: offset) {[weak self] (response, error) in
                        if let response = response {
                            self?.isFetching = false
                            if self?.offset == 0{
                                // fetch response from server for first fetch
                                self?.notificationsResponse = response
                                if self?.refreshControl.isRefreshing ?? false {
                                    self?.refreshControl.endRefreshing()
                                }
                            }else{
                                // append if already exist ( pagination )
                                self?.notificationsResponse?.notifications.append(contentsOf: response.notifications)
                            }
                            self?.collectionView.reloadData()
            
                        }
            
                    }
            }
            
            
            // MARK: - Collection View Delegate
            func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
            
                guard let anyList = responseFromServer else { return }
                // check if scroll reach last index available and keep fetching till our model list has all entries from server
                if indexPath.item == anyList.count - 1 && anyList.count  < totalListOnServerCount{
            
                    offset += pageSize
                    fetchNotifications()
            
                }
            }
            

            【讨论】:

              【解决方案14】:

              Swift 5(全面全面的分页解决方案)

              用户界面代码: https://github.com/eonist/PaginationTable

              数据模型代码: https://github.com/eonist/PaginationService

              核心组件:

              • rowData:此数组将在每个滚动结束事件上增长,直到它从后端 API 加载所有项目
              • paginationAmount:每个分页周期获取的数量
              • paginationIndex:当前单元格数量(随着您加载更多数据而增长
              • isFetching:一个布尔值,让代码知道数据是否已经加载,以避免重复获取等 fetchData:模拟从 remote-api 获取数据 陷阱:

              示例代码不依赖于后端。它只是使用文件中的数据进行测试,并通过休眠几秒钟来模拟网络调用 该示例使用了一些依赖项以加快此示例的创建速度。但它的基本内容,如 AFNetwork、Json 解析、Autolllayout。所有这些都可以轻松替换 要求:

              可以提供项目计数的后端 API 可以返回范围内项目的后端 API(startIndex、endIndex)

              【讨论】:

                猜你喜欢
                • 2018-08-21
                • 1970-01-01
                • 1970-01-01
                • 2016-12-02
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-02-27
                • 2017-06-29
                相关资源
                最近更新 更多