22

An API I am writing has about 2000 records, returned in JSON via a simple RESTful API I have written.

To reduce issues with lots of data, I want to use pagination so that I only return say the first 10 or first 20 per request via like an offset or limit or page, etc.

But my question is how does the iOS UITableView know when to get the next page of results?

I really am unsure of how to do this. The user could be scrolling superfast and so the API might not have enough time to retrieve 20 or 50 records at a time.

Another issue related to this is, lets say the user scrolls down on the UITableView, then up and then back down again -- how do you prevent the API from firing multiple times for the same rows?

Thanks

zardon
  • 301
  • 1
  • 2
  • 9

2 Answers2

21

Seems to be you aren't thinking in terms of MVC . Your UITableView has very little do with paging and webrequest. It's just concerned about its datasource not pages.

Restuful API Design : Assume your web Request is designed as follow :

/getRecorods?pageSize=20&pageNo=2

This will return you an Array of JSON. In addition to that its helpful to have a count parameter and a link to next page. This helps in parsing and sync with web server.

how do you prevent the API from firing multiple times for the same rows?

A simple flag is sufficient to avoid loading multiple pages. Just make sure that flag is accessed in main thread. The actual webrequest needs to go in background thread.

Below is the code you need to put into your UITableViewController which loads the data


- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    //call http Util here to load the data
    httpUtil.delegate = self;

    //This retrieves post for first page always
    currentPageNumber = 1;
    [httpUtil getRecords:currentPageNumber];

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    int retValue = 0;
    if(recordsArray != nil){
        retValue = [recordsArray count];
    }
    return retValue;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {

    }

    // Configure the cell using recordsArray objectAtIndex

    return cell;
}


- (void)scrollViewDidScroll:(UIScrollView *)scrollView{

      if(self.tableView.contentOffset.y >= (self.tableView.contentSize.height - self.tableView.bounds.size.height)) {

      //NSLog(@" scroll to bottom!");
      if(isPageRefresing == NO){ // no need to worry about threads because this is always on main thread.

        isPageRefresing = YES;
        [self showMBProfressHUDOnView:self.view withText:@"Please wait..."];
        currentpagenumber = currentpagenumber +1;
        [httpUtil getRecords:currentpagenumber];
       }
    }

}

// you can get pageNo from tag
// make sure this is called in main thread
-(void)didFinishRecordsRequest:(NSArray *)results forPage:(NSInteger)pageNo{
    if(pageNo == 1){
        recordsArray = [results mutableCopy];
    }
    else{
        [recordsArray addObjectsFromArray:results];
    }
    isPageRefresing = NO;
    [self.tableView reloadData];
}


-(void)didFailedChalkBoardRequestWithError:(NSError *)error{

    //If Since subsequent refresh calls(for page 2 or page 3 fails)
    //then undo the current page number
    currentpagenumber--;
    isPageRefresing = NO;
}

// HTTP Utility class
-(void)getRecords:(NSInteger)pageNumber{

    NSString *serverUrl = [NSString stringWithFormat:@"http://yourwebsite.com/page/%d /?json",pageNumber];

    NSLog(@"fetching Stories data from server WITH URL %@",serverUrl);
    NSURL *url = [NSURL URLWithString:serverUrl];
    storiesRequest = [ASIHTTPRequest requestWithURL:url];
    storiesRequest.tag = pageNumber;
    [storiesRequest setDelegate:self];
    [storiesRequest startAsynchronous];
}

Kunal Balani
  • 4,739
  • 4
  • 36
  • 73
  • This makes sense, I will give it a try using the data source I have. – zardon Mar 20 '14 at 10:27
  • The issue was Asncyronous table view, which I am currently researching now. Many thanks, accepted answer – zardon Mar 25 '14 at 12:12
  • what will be http util and getrecorde in my case i declare url by this line " NSURL *theURL = [NSURL URLWithString:@"http://qa.networc.in:1336/api/dispatcher/rideHistory/1"];" – sandeep tomar May 30 '16 at 06:11
  • @Kunal Would you call the `didFinishRecordsRequest` and `didFailedChalkBoardRequestWithError` in the `getRecords` method? Like in the `getRecords` method if the callback fails when executing the async get request to the server you would call the `didFailedChalkBoardRequestWithError` method and if the callback succeeds then you would call the `didFinishRecordsRequest` method. Correct? –  Aug 01 '16 at 21:10
  • @1290 this a design question. I would create get record which itself has a callback. Normally in async programming u always cascade error up to the highest level possible. – Kunal Balani Aug 02 '16 at 01:15
  • @Kunal If you don't mind can you please provide an example in your code by what you mean when you say: "I would create get record which itself has a callback." Are you talking about creating your own callback?I was thinking to use a library like AlamoFire and call the `didFinishRecordsRequest` and `didFailedChalkBoardRequestWithError` methods in the callback of the `AlamoFire` get request method itself instead of creating my own callback. –  Aug 02 '16 at 05:37
0

For Swift

func scrollViewDidScroll(scrollView: UIScrollView) {
        if collectionViewGif.contentOffset.y >= self.collectionViewGif.contentSize.height - self.collectionViewGif.frame.size.height
        {
            offset = offset + 1
            self.fetchGifWithPaination(defaultText)
        }
    }
Maninderjit Singh
  • 1,419
  • 1
  • 13
  • 23