2
votes

My iOS app is using a UIPageViewcontroller with about 18 pages. Each page has a UITableView in it with 4 dynamic cells. Each cell has about 10 different images.

The problem is that each time I swipe the UIPageViewcontroller to go to the next/previous page, there's like a half-second delay that makes the user experience pretty bad.

It seems like the UITableView of the next page to be displayed after the swipe is loading all its heavy-data cells and that originates this delay.

I've read it's possible to make this loading asyncronous but I have no idea about how to do this.

Any of you guys have any idea about how to avoid this delay so the transition to pages runs smoothly?

Here's some code for the UITableView:

#pragma mark - Table view data source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return 4;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if ([cellsStatus[indexPath.row] integerValue] == CLOSED_CELL)
        return [GrintScoreTrackPlayerDynamicCell getHeightForCell];
    else
        return [GrintScoreTrackPlayerDynamicCell getFullHeightForCell];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if ([mainController respondsToSelector:@selector(tableViewDidScroll:)] &&
        [mainController isKindOfClass:[GrintScoreTrackViewController class]])
    {
        [mainController tableViewDidScroll:scrollView];
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier;
    GrintScoreTrackPlayerDynamicCell *cell, *cellOwner;

    // Cell ID
    CellIdentifier = @"GrintScoreTrackPlayerDynamicCell-ID";

    // Creating the cell
    cell = (GrintScoreTrackPlayerDynamicCell*)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // Creating the cell owner
    cellOwner = [[GrintScoreTrackPlayerDynamicCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];

    // Getting cell from Nib, if possible.
    if (cell == nil)
    {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"GrintScoreTrackPlayerDynamicCell" owner:cellOwner options:nil];
        cell = [nib objectAtIndex:0];
    }

    // Filling cell with the corresponding user info
    [cell fillCellWithContent];

    // Changing appearance based on the new status
    [self setStatusOfCell:cell withStatus:[cellsStatus[indexPath.row] integerValue]];

    // Setting action for cell's "collapseButton" button
    [cell.collapseButton addTarget:self action:@selector(changeCellStatus:) forControlEvents:UIControlEventTouchUpInside];

    // Setting action for cell's "editScoresButton" button
    [cell.editScoresButton addTarget:self action:@selector(editScoresButtonWasPressed:) forControlEvents:UIControlEventTouchUpInside];

    // Setting action for cell's "pickerEnterButton" button
    [cell.pickerEnterButton addTarget:self action:@selector(pickerEnterButtonWasPressed:) forControlEvents:UIControlEventTouchUpInside];

    // Setting cell selection style
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.layoutMargins = UIEdgeInsetsZero;

    // Setting picker view delegate and data source.
    [cell.pickerView setDelegate:self];
    [cell.pickerView setDataSource:self];

    return cell;
}

And some code from the GrintScoreTrackPlayerDynamicCell cell:

- (void) fillCellWithContent
{
    // Doing some processing fixes
    [self postProcessCellContent];
}

- (void) postProcessCellContent
{
    CGSize textSize;
    CGFloat scoreLabelTextWidth, newHazzardLabelXValue, newPuttsLabelXValue, offset;

    // Setting an offset for the hazzard and putts label. This offset represents the distance between the score label text and the hazzard and putts labels.
    offset = 1;

    // Getting the width of the text that's inside the Score Label.
    textSize = [[self.scoreLabel text] sizeWithAttributes:@{NSFontAttributeName:[self.scoreLabel font]}];
    scoreLabelTextWidth = textSize.width;

    // Getting new x position value for the Hazzards Label.
    newHazzardLabelXValue = self.scoreLabel.frame.origin.x +
                            (self.scoreLabel.frame.size.width - scoreLabelTextWidth) * 0.5 -
                            self.hazardsLabel.frame.size.width - offset;

    // Setting the new x position value for the Hazzards label
    [GrintUtils moveView:self.hazardsLabel inX:newHazzardLabelXValue andY:self.hazardsLabel.frame.origin.y];

    // Getting new x position value for the Putts Label.
    newPuttsLabelXValue = self.scoreLabel.frame.origin.x +
                         (self.scoreLabel.frame.size.width - scoreLabelTextWidth) * 0.5 +
                          scoreLabelTextWidth + offset;

    // Setting the new x position value for the Putts label
    [GrintUtils moveView:self.puttsLabel inX:newPuttsLabelXValue andY:self.puttsLabel.frame.origin.y];
}

Here's some code about my UIPageViewController with the pages pre-loaded in pageContent array:

#pragma mark - UIPageViewController Data Course

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
    // Getting the previous of the next controller
    int index;
    index = currentPageBeingDisplayed - 1;
    if (index < 0)
        index = [pageContent count] - 1;

    return pageContent[index];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    // Getting the index of the next controller
    int index;
    index = currentPageBeingDisplayed + 1;
    index = index % [pageContent count];

    return pageContent[index];
}

A screenshot of Instruments Tool Counter focused on the delay:

enter image description here

An finally 2 pictures (it has 2 states) of the cell:

enter image description here

enter image description here

1
You need to show your fillCellWithContent method. The other thing you should do is pre-load the "next" view controller in your pageViewController - that way it will probably have loaded all of its data before it is displayed. Also fetching the data in the cellForRowAtIndexPath is not ideal - you should pre-fetch the data in viewDidLoad or similarPaulw11
And use Instruments! It will tell you exactly where that "delay" is being spent.matt
Hi @Paulw11 , I just editted the code to add the fillCellWithContent method... On the other hand, I have preloaded all the pages of my UIPageViewController in and array. So I create them just once. I just added some code about it too... I don't get any data from a server yet. Everything is local.jmoukel
@jmoukel Are you kidding? You are doing great without any help at all! Excellent detective work. Well done. - So, just to be clear, how do these images get into the cell? I don't see any code that does that; are you saying it happens because the images are in the cell-defining .xib?matt
@jmoukel That's enough info for me to make a suggestion - see my answer below. Might not work, but I think it will! :)matt

1 Answers

1
votes

This is going to be a wild guess - I haven't tried it. But from what you've so brilliantly deduced, and from what you've said in your comments, I take it that this is how the images get into the cells: they are in the cell-defining .xib.

So, for every row of the table, the .xib is loaded and the images must all be loaded freshly from the .xib. This is what we believe is slowing you down.

So here's a suggestion: don't do that. Instead, just have empty image views in the cell, and populate them with images in cellForRowAtIndexPath:.

Why would this help? Because if you fetch an image by calling imageNamed:, the image is cached by the system. Thus each image will be fetched once and will be cached forever after.

One more suggestion - make sure the images are small, i.e. the same size as they will be displayed. It is a huge waste of time to start with a large image and display it in a small image view, because the version of the image to be displayed must be calculated every single time.