scientiffic
scientiffic

Reputation: 9415

iOS CollectionView: Loading Data Asynchronously

I'm using a collection view and trying to transition from loading the data synchronously to loading it asynchronously.

I know that the following currently works (it takes a while to load, but all the cells appear correctly when it's done):

// load projectData in main thread
NSData * projectData = [NSData dataWithContentsOfURL:userUrl];
[self performSelectorOnMainThread:@selector(fetchProjects:)withObject:projectData waitUntilDone:YES];

I rewrote it to do everything asynchronously:

// load project data asynchronously
 dispatch_async(bgQueue, ^{
     UIView *loadingAnimation = loadingCircle;
     loadingAnimation.tag = 15;
     [self.collectionView addSubview:loadingAnimation];
     [loadingCircle startAnimating];

     NSData * projectData = [NSData dataWithContentsOfURL:userUrl];

     [self performSelector:@selector(fetchProjects:) withObject:projectData];

     dispatch_async(dispatch_get_main_queue(), ^{
         NSLog(@"finished with loading projects");
         UIView *viewToRemove = [self.view viewWithTag:15];
         [viewToRemove removeFromSuperview];
         [self.collectionView reloadData];
     });
 });

When I run the app after loading the data asynchronously, the view appears empty (the cells have no content), but when I scroll, some of the cells begin to appear.

Is there anything else I need to call besides reloadData to get my collection cells to appear properly?

Here is my fetchProjects:

// get JSON data of projects
- (void)fetchProjects:(NSData *)responseData {
    NSError * error;
    NSDictionary * json = [NSJSONSerialization
                           JSONObjectWithData:responseData
                           options:kNilOptions
                           error:&error]; // get dictionary from json data
    NSDictionary * data = [json objectForKey:@"data"]; // get data in array
    NSArray * projects = [data objectForKey:@"projects"];
    NSDictionary * mostRecentProject = [projects objectAtIndex:0];

    mostRecentProjectID = [mostRecentProject objectForKey:@"id"];

    for (NSDictionary *currentProject in projects)
    {
        [projectIDs addObject: [currentProject objectForKey:@"id"]];
        NSString *projectTitle = [currentProject objectForKey:@"title"];
        NSString *trimmedProjectTitle = [projectTitle stringByTrimmingCharactersInSet:
                                         [NSCharacterSet whitespaceAndNewlineCharacterSet]];
        id delegate = [[UIApplication sharedApplication] delegate];
        self.managedObjectContext = [delegate managedObjectContext];
        Project *newProject = (Project *) [NSEntityDescription insertNewObjectForEntityForName:@"Project" inManagedObjectContext:[self managedObjectContext]];

        CustomLabel *cellLabel=[[CustomLabel alloc]init];
        cellLabel.text = trimmedProjectTitle;
        NSLog(@"fetchprojects:%@",projectTitle);
        [titles addObject:projectTitle];

        CGSize maxLabelSize = CGSizeMake(screenWidth/2 - 30,100);

        CustomLabel *titleLabel = [[CustomLabel alloc]init];
        [titleLabel setNumberOfLines:0];
        titleLabel.text = projectTitle;

        CGSize expectedLabelSize = [titleLabel.text sizeWithFont:titleLabel.font constrainedToSize:maxLabelSize lineBreakMode:NSLineBreakByWordWrapping];

        CGRect labelFrame = (CGRectMake(0, 0, screenWidth/2 - 30, 0));
        labelFrame.origin.x = 0;
        labelFrame.origin.y = screenWidth/2 - 70 - expectedLabelSize.height;
        labelFrame.size.height = expectedLabelSize.height;
        titleLabel.frame = labelFrame;

        titleLabel.backgroundColor = [[UIColor blackColor]colorWithAlphaComponent:0.5f];
        titleLabel.textColor =[UIColor whiteColor];
        [titleLabel setFont: [UIFont fontWithName: @"HelveticaNeue" size:12]];
        //NSLog(@"%@", titleLabel.text);

        UIImageView *imagePreview = [[UIImageView alloc] initWithFrame:CGRectMake(7.5, 10, screenWidth/2 -30, screenWidth/2 -70)];
        imagePreview.contentMode= UIViewContentModeScaleAspectFill;
        imagePreview.clipsToBounds = YES;
        [imagePreview setImage:[UIImage imageNamed:@"blank.png"]];
        [imagePreview addSubview:titleLabel];
        [imagePreview.subviews[0] setClipsToBounds:YES];
        [projectContainers addObject: imagePreview];
}
}

Upvotes: 1

Views: 1844

Answers (1)

Mike S
Mike S

Reputation: 42325

You're doing a lot of UI work on a background thread which you really shouldn't do. From what I can see, the only line that really needs to be run on a background thread is this one:

NSData * projectData = [NSData dataWithContentsOfURL:userUrl];

The rest looks like it deals with setting up and displaying your UI and some CoreData stuff; all of that needs to be run on the main thread. The easiest way to do that and keep everything running in the right order would be something like this:

// NOTE: If you're sure you're already on the main thread here, you don't need the dispatch, but it's not going to hurt to leave it in.
dispatch_async(dispatch_get_main_queue(), ^{
    UIView *loadingAnimation = loadingCircle;
    loadingAnimation.tag = 15;
    [self.collectionView addSubview:loadingAnimation];
    [loadingCircle startAnimating];
});

dispatch_async(bgQueue, ^{
    NSData * projectData = [NSData dataWithContentsOfURL:userUrl];

    dispatch_async(dispatch_get_main_queue(), ^{
        [self fetchProjects:projectData];

        NSLog(@"finished with loading projects");
        UIView *viewToRemove = [self.view viewWithTag:15];
        [viewToRemove removeFromSuperview];
        [self.collectionView reloadData];
    });
});

Note: I also changed [self performSelector:@selector(fetchProjects:) withObject:projectData] to [self fetchProjects:projectData]; you don't really need to go through performSelector: there.

Upvotes: 2

Related Questions