ios Swift tableView 分页

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35367496/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 08:29:24  来源:igfitidea点击:

Swift tableView Pagination

iosswiftuitableviewpagination

提问by SwiftDeveloper

I have success working tableview with json parsing codes.But may have 1000 more item so need pagination when scrolling bottom side. I dont know how can i do this my codes under below. For objective-c have a lot of example but for swift i didnt find working example. Im waiting your helps. I think will be help too many people. Thank you !

我使用 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()
    }


}

回答by Warif Akhand Rishi

For that you need to have server side change also.

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

  1. Server will accept fromIndexand batchSizein the APIurl as query param.

    let listUrlString =  "http://bla.com/json2.php?listType=" + listType + "&t=" + NSUUID().UUIDString + "&batchSize=" + batchSize + "&fromIndex=" + fromIndex
    
  2. In the server response, there will be an extra key totalItems. This will be used to identify all items are received or not. An array or items fromIndexto batchSizenumber of items.

  1. 服务器将接受fromIndexbatchSizeAPIurl 中作为查询参数。

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

In the app side

在应用端

  1. First loadItem()will be called with fromIndex = 0and batchSize = 20(for example in viewDidLoad()or viewWillAppear). removeAll items from privateListarray before calling loadItem()for the first time

  2. Server returns an array of first 20 items and totalItemstotal number of items in the server.

  3. Append the 20 items in privateListarray and reload tableView

  4. In tableView:cellForRowAtIndexPathmethod check if the cell is the last cell. And check if totalItems(form server) is greater than privateList.count. That means there are more items in the server to load

    if indexPath.row == privateList.count - 1 { // last cell
        if totalItems > privateList.count { // more items to fetch
            loadItem() // increment `fromIndex` by 20 before server call
        }
    }
    
  1. FirstloadItem()将使用fromIndex = 0and调用batchSize = 20(例如 in viewDidLoad()or viewWillAppear)。在第一次privateList调用之前从数组中删除所有项目loadItem()

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


Question:where is refresh ? will be scrolling ?

题:where is refresh ? will be scrolling ?

Refresh after appending new items in the array when server response received. (step 3)

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

Scrolling will trigger tableView:cellForRowAtIndexPathfor every cell when user scrolls. Code is checking if it is the last cell and fetch remaining items. (step 4)

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

Sample project added:
https://github.com/rishi420/TableViewPaging

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

回答by Mahesh Giri

The good and efficient way to do it is by using scrollviewDelegatein tableview Just add UIScrollViewDelegatein your viewControllerIn view controller

这样做的好方法是scrollviewDelegate在 tableview 中使用只需添加UIScrollViewDelegate到您的viewControllerIn 视图控制器中

//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
}

Override two methods from this delegate

覆盖此委托的两个方法

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)

                }
            }


    }

回答by MhmdRizk

SWIFT 3.0 - 4...

SWIFT 3.0 - 4...

If you're sending the page number in the API request then this is the ideal way for implementing pagination in your app.

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

1) declare the variable current Page with initial Value 0 and a bool to check if any list is being loaded with initial value false

1) 用初始值 0 和一个 bool 声明变量 current Page 以检查是否有任何列表正在加载初始值 false

var currentPage : Int = 0
var isLoadingList : Bool = false

2) This is the function that gets the list example:

2)这是获取列表示例的函数:

 func getListFromServer(_ pageNumber: Int){
 self.isloadingList = false
 self.table.reloadData()
 } 

3) This is the function that increments page number and calls the API function

3)这是增加页码和调用API函数的函数

 func loadMoreItemsForList(){
 currentPage += 1
 getListFromServer(currentPage)
 }

4) this is the method that will be called when the scrollView scrolls

4)这是scrollView滚动时会调用的方法

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if (((scrollView.contentOffset.y + scrollView.frame.size.height) > scrollView.contentSize.height ) && ! isLoadingList){
        self. isLoadingList = true
        self. loadMoreItemsForList()
    }
}

P.S. the bool isLoadingList role is to prevent the scroll view from getting more lists in one drag to the bottom of the table view.

PS bool isLoadingList 作用是防止滚动视图在一次拖拽到表格视图底部时获取更多列表。

回答by Paul Robinson

This is now a little bit easier with the addition of a new protocol in iOS10: UITableViewDataSourcePrefetching

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

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

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

回答by Satyendra Singh Bhati

//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()
        }
    }
}`

回答by Андр?й Погор?лко

I've tried an approach with willDisplayCell. But it produces unwanted stops during scrolling which makes the user experience not good. I think a better way is to do it in scrollViewDidEndDecelerating delegate method. It calls when the scroll finishes and only then new data comes. User sees that there is new content and scroll again if he wants. I've taken the answer herebut instead of scrollViewDidEndDragging I use scrollViewDidEndDecelerating. It looks just better in my case. Here is some code from my project.

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

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

回答by scollaco

I needed something similar on a project and my solution was:

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

1 - create a variable numberOfObjectsInSubArray (initial value 30 or whatever you want)

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

2 - create a subarray to add a number of objects from your privateList array every time i tap "show more"

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

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

And use it on

并将其用于

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

3- Whenever you need to show more objects, do:

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

I hope it helps

我希望它有帮助

回答by Sumeet

Add another section to your tableview, let this section have only 1 row which will be a cell containing an activity indicator, to denote loading.

将另一个部分添加到您的 tableview,让该部分只有 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()
    }
}

回答by Shehzad Ali

Another way of doing this is: You may set a threshold for getting elements while sending request each time:

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

Lets say you you are fetching 20 elements first time. You will be saving last fetched record id or number for getting list of next 20 elements.

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

let lastFetchedIndex = 20;

I am assuming that you have already added these records in your myArray. MyArray is the dataSource of tableView. Now myArray is containing 40 objects. I am going to make a list of indexPaths of rows that needs to be inserted in tableView now.

我假设您已经在 myArray 中添加了这些记录。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)

}

Here I am updating my tableView. Make sure your dataSource i mean your myArray has already been updated. So that it may insert rows properly.

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

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

回答by SwiftyIso

here is a sample code for collection view :

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

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