0

I have a UITableView with many rows(+1000). I get the rows of these UITableView once by using NSFetchedResultsController and fetchBatchSize in viewDidLoad as below:

@interface MessagesViewController ()  
@property (nonatomic, strong) NSFetchedResultsController *messagesFRC;
@end



@implementation MessagesViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    if (self.messagesFRC == nil) {

        // Read in Model.xcdatamodeld
        NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
        NSPersistentStoreCoordinator *psc =
        [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];

        // Where does the SQLite file go?
        NSArray *documentDirectories =
        NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                            NSUserDomainMask,
                                            YES);
        // Get one and only document directory from that list
        NSString *documentDirectory = [documentDirectories firstObject];
        NSString *path = [documentDirectory stringByAppendingPathComponent:@"model.sqlite"];

        NSURL *storeURL = [NSURL fileURLWithPath:path];
        NSError *error = nil;
        if (![psc addPersistentStoreWithType:NSSQLiteStoreType
                               configuration:nil
                                         URL:storeURL
                                     options:nil
                                       error:&error]) {
            @throw [NSException exceptionWithName:@"OpenFailure"
                                           reason:[error localizedDescription]
                                         userInfo:nil];
        }

        // Create the managed object context
        NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
        context.persistentStoreCoordinator = psc;

        NSFetchRequest *request = [[NSFetchRequest alloc] init];

        NSString *entityName = @"Message";
        NSString *sortAttribute = @"timestamp";


        NSEntityDescription *e = [NSEntityDescription entityForName:entityName
                                             inManagedObjectContext:context];

        request.entity = e;


        NSSortDescriptor *sd = [NSSortDescriptor
                                sortDescriptorWithKey:sortAttribute
                                ascending:NO];
        request.sortDescriptors = @[sd];

//        request.fetchLimit = 30;
        request.fetchBatchSize = 60;


        self.messagesFRC = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];


        NSError *error3 = nil;
        if (![self.messagesFRC performFetch:&error3]) {
            NSLog(@"Failed to initialize FetchedResultsController: %@\n%@", [error localizedDescription], [error userInfo]);
            abort();
        }

        self.messagesFRC.delegate = self;
    }
}

@end

Also I set the height of each cells with heightForRowAtIndexPath in this controller:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{        
    if ([tableView isEqual:self.tableView]) {
        NSManagedObject *row = [self.messagesFRC objectAtIndexPath:indexPath];
        NSString *messageText = [[NSString alloc] initWithData:[row valueForKey:@"text"] encoding:NSUTF8StringEncoding];
        messageText = [[GeneralHelper convertHtmlToString:messageText] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
        //        messageText = @"yes\r\nnew";

        NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
        paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
        paragraphStyle.alignment = NSTextAlignmentRight;
        //        paragraphStyle.


        NSDictionary *attributes = @{NSFontAttributeName: self.messageFont,
                                     NSParagraphStyleAttributeName: paragraphStyle}; // TODO: Font


        CGFloat width = CGRectGetWidth(tableView.frame)-kMessageTableViewCellAvatarHeight;
        width -= 25.0;

        CGRect titleBounds = [[row valueForKey:@"title"] boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:NULL];
        CGRect bodyBounds = [messageText boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:NULL];

        if (messageText.length == 0) {
            return 0.0;
        }

        CGFloat height = CGRectGetHeight(titleBounds);
        height += CGRectGetHeight(bodyBounds);
        height += 40.0;

        if (height < kMessageTableViewCellMinimumHeight) {
            height = kMessageTableViewCellMinimumHeight;
        }

        return height;
    }
    else {
        return kMessageTableViewCellMinimumHeight;
    }
}

The problem is that loading the UITableView taking a long time (more than 15 seconds) because of setting height of all cells at the beginning of workflow. So I need lazy loading on heightForRowAtIndexPath for each 30 cells, and then by scrolling up and down, get the next 30 cells height.

In addition, I check the UITableViewAutomaticDimension for iOS 7+, but it had very high CPU usage:

-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
   return UITableViewAutomaticDimension;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
   return UITableViewAutomaticDimension;
}

How to solve this problem?

Soheil Novinfard
  • 1,358
  • 1
  • 16
  • 43
  • You can try using `- tableView:estimatedHeightForRowAtIndexPath:` – Avi Feb 03 '16 at 09:23
  • Please read it again, in the last part of question I mentioned it. – Soheil Novinfard Feb 03 '16 at 09:29
  • Also, the table view only loads the visible cells, so it's only going to call `heightForRowAtIndexPath` for those. If you log or set a breakpoint, you can see this for yourself. – Avi Feb 03 '16 at 09:45
  • @Avi +1000 logs for heightForRowAtIndexPath – Soheil Novinfard Feb 03 '16 at 09:56
  • You should not return `...AutomaticDimension` for the estimated height. You should return the estimated height. So `return 100;` for instance. You should try profiling the app with the automatic dimension and seeing **WHY** it is using a lot of CPU. Have you created your cells properly with auto layout constraints from top to bottom? – Fogmeister Feb 03 '16 at 10:15

2 Answers2

0

You can use a counter say numberOfRenderedRows. numberOfRenderedRows is set to 30 in start and that will be the added by 30 everytime user will scroll the table and refreshes. This will be count of the tableview rows. Use following to add refreshing control.

#

UIRefreshControl* refreshControl = [[UIRefreshControl alloc]init];
[refreshControl addTarget:self action:@selector(actionRefreshRows) forControlEvents:UIControlEventValueChanged];
[tableView addSubview:refreshControl];
Safwan Ahmed
  • 205
  • 2
  • 7
  • Could you please explain it more ? Some examples or documentation? Where to use it exactly ? Thanks – Soheil Novinfard Feb 03 '16 at 10:43
  • Check following links for explanation of UIRefreshControl ## http://code.tutsplus.com/tutorials/working-with-uirefreshcontrol--mobile-14712 ## https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIRefreshControl_class/ – Safwan Ahmed Feb 06 '16 at 17:26
0

Maybe you should try to make your cells self sizing.

By setting proper constraints to your prototype cell, you can get rid of your heightForRowAtIndexPath method.

The documentation is here.

Crazyrems
  • 2,551
  • 22
  • 40