3

I'm having trouble updating my old code which made synchronous JSOn calls to a new one which makes asynchronous calls using AFNetworking.

In my old code I was grouping cells with a UICollectionReusableView using a date string ("release_date"), all of this was done in the viewDidLoad. Now with AFNetworking I moved everything out of the viewDidLoad, so I'm stuck trying to figure out how to merge my old code with my new one.

This is the new code I have to parse my JSON with AFNetworking:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.upcomingReleases = [[NSMutableArray alloc] init];

    [self makeReleasesRequests];

    [self.collectionView registerClass:[ReleaseCell class] forCellWithReuseIdentifier:@"ReleaseCell"];
}

-(void)makeReleasesRequests  //AFNetworking Call
{
    NSURL *url = [NSURL URLWithString:@"http://www.soleresource.com/upcoming.json"];

    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];

    operation.responseSerializer = [AFJSONResponseSerializer serializer];

    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

        NSLog(@"@");

        self.upcomingReleases = [responseObject objectForKey:@"upcoming_releases"];

        [self.collectionView reloadData];

    } failure:nil];

    [operation start];
}

Code I had in my viewDidLoad before I started using AFNetworking to make JSON calls and "group" my cells:

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSURL *upcomingReleaseURL = [NSURL URLWithString:@"http://www.soleresource.com/upcoming.json"];

    NSData *jsonData = [NSData dataWithContentsOfURL:upcomingReleaseURL];

    NSError *error = nil;

    NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];

    NSArray *upcomingReleasesArray = [dataDictionary objectForKey:@"upcoming_releases"];

    //This is the dateFormatter we'll need to parse the release dates
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
    NSTimeZone *est = [NSTimeZone timeZoneWithAbbreviation:@"EST"];
    [dateFormatter setTimeZone:est];
    [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]]; //A bit of an overkill to avoid bugs on different locales

    //Temp array where we'll store the unsorted bucket dates
    NSMutableArray *unsortedReleaseWeek = [[NSMutableArray alloc] init];
    NSMutableDictionary *tmpDict = [[NSMutableDictionary alloc] init];

    for (NSDictionary *upcomingReleaseDictionary in upcomingReleasesArray) {

        //We find the release date from the string
        NSDate *releaseDate = [dateFormatter dateFromString:[upcomingReleaseDictionary objectForKey:@"release_date"]];

        //We create a new date that ignores everything that is not the actual day (ignoring stuff like the time of the day)
        NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
        NSDateComponents *components =
        [gregorian components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:releaseDate];

        //This will represent our releases "bucket"
        NSDate *bucket = [gregorian dateFromComponents:components];

        //We get the existing objects in the bucket and update it with the latest addition
        NSMutableArray *releasesInBucket = [tmpDict objectForKey:bucket];
        if (!releasesInBucket){
            releasesInBucket = [NSMutableArray array];
            [unsortedReleaseWeek addObject:bucket];
        }

        UpcomingRelease *upcomingRelease = [UpcomingRelease upcomingReleaseWithName:[upcomingReleaseDictionary objectForKey:@"release_name"]];
        upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:@"release_date"];
        upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:@"release_date"];
        [releasesInBucket addObject:upcomingRelease];
        [tmpDict setObject:releasesInBucket forKey:bucket];
    }

    [unsortedReleaseWeek sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        NSDate* date1 = obj1;
        NSDate* date2 = obj2;
        //This will sort the dates in ascending order (earlier dates first)
        return [date1 compare:date2];
        //Use [date2 compare:date1] if you want an descending order
    }];

    self.releaseWeekDictionary = [NSDictionary dictionaryWithDictionary:tmpDict];
    self.releaseWeek = [NSArray arrayWithArray:unsortedReleaseWeek];

    [self.collectionView registerClass:[ReleaseCell class] forCellWithReuseIdentifier:@"ReleaseCell"];
}

CollectionViewCell

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *identifier = @"Cell";

    ReleaseCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];

    // Part of my new code AFNetworking
    NSDictionary *upcomingReleaseDictionary = [self.upcomingReleases objectAtIndex:indexPath.row];
    //

    // I had this in my old code
    UpcomingRelease *upcomingRelease = [self.releaseWeekDictionary objectForKey:self.releaseWeek[indexPath.section]][indexPath.row];
    //
    cell.release_name.text = upcomingRelease.release_name;

    return cell;
}

This is the rest:

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return [self.releaseWeek count];
}

-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return [[self.releaseWeekDictionary objectForKey:self.releaseWeek[section]] count];
}

-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
    ReleaseWeek *releaseWeek = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"releaseWeek" forIndexPath:indexPath];

    //We tell the formatter to produce a date in the format "Name-of-the-month day"
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"MMMM dd"];
    NSTimeZone *est = [NSTimeZone timeZoneWithAbbreviation:@"EST"];
    [dateFormatter setTimeZone:est];
    [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]];

    //We read the bucket date and feed it to the date formatter
    NSDate *releaseWeekDate = self.releaseWeek[indexPath.section];

    releaseWeek.releaseDate.text = [[dateFormatter stringFromDate:releaseWeekDate] uppercaseString];
    return releaseWeek;
}

I'm basically trying to figure out how to take the code that grouped my cells be the date string and integrate it with my new code.

Thanks.

ChrisBedoya
  • 737
  • 2
  • 15
  • 28

2 Answers2

2

Think MVC. Separate your model (the stuff that comes from the network) into a separate class that does the network operations, and grouping things into arrays and dictionaries. Your view controller should simply observe the model. You can use delegation or (my favorite) KVO to know when the model has updated data available. Then you just update your collection view. Your view controller should simply be an interface between the model and the views. If you separate things out this way you will find that it is much more natural, and you aren't fighting against the system.

jsd
  • 7,673
  • 5
  • 27
  • 47
2

You are closer than you think.

Simply put everything you used to do in viewDidLoad: (everything between the assignment of jsonData to the registration of your collection view class) into the callback block of your AFNetworking call (where jsonData is now called responseObject).

At the end of the callback block, simply invoke [self.collectionView reloadData], and the your collection view will reload itself (i.e. call numberOfItems and cellForItemAtIndexPath for each item).

In your UICollectionViewDataSource methods that return the number of sections and items, simply return 0 if the properties that hold your model are nil or empty.

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    // Correctly returns 0 if nil or empty.
    return [self.releaseWeek count];
}

-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    if(!self.releaseWeekDictionary[section] || !self.releaseWeek[section]) {
        return 0;
    }else{
         return [self.releaseWeekDictionary[self.releaseWeek[section]] count];
    }
}

Below should be the gravy code in your completion block.

[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

        NSLog(@"@");

        NSArray *upcomingReleasesArray = [dataDictionary objectForKey:@"upcoming_releases"];

        //This is the dateFormatter we'll need to parse the release dates
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
        NSTimeZone *est = [NSTimeZone timeZoneWithAbbreviation:@"EST"];
        [dateFormatter setTimeZone:est];
        [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]]; //A bit of an overkill to avoid bugs on different locales

        //Temp array where we'll store the unsorted bucket dates
        NSMutableArray *unsortedReleaseWeek = [[NSMutableArray alloc] init];
        NSMutableDictionary *tmpDict = [[NSMutableDictionary alloc] init];

        for (NSDictionary *upcomingReleaseDictionary in upcomingReleasesArray) {

            //We find the release date from the string
            NSDate *releaseDate = [dateFormatter dateFromString:[upcomingReleaseDictionary objectForKey:@"release_date"]];

            //We create a new date that ignores everything that is not the actual day (ignoring stuff like the time of the day)
            NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
            NSDateComponents *components =
            [gregorian components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:releaseDate];

            //This will represent our releases "bucket"
            NSDate *bucket = [gregorian dateFromComponents:components];

            //We get the existing objects in the bucket and update it with the latest addition
            NSMutableArray *releasesInBucket = [tmpDict objectForKey:bucket];
            if (!releasesInBucket){
                releasesInBucket = [NSMutableArray array];
                [unsortedReleaseWeek addObject:bucket];
            }

            UpcomingRelease *upcomingRelease = [UpcomingRelease upcomingReleaseWithName:[upcomingReleaseDictionary objectForKey:@"release_name"]];
            upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:@"release_date"];
            upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:@"release_date"];
            [releasesInBucket addObject:upcomingRelease];
            [tmpDict setObject:releasesInBucket forKey:bucket];
        }

        [unsortedReleaseWeek sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
            NSDate* date1 = obj1;
            NSDate* date2 = obj2;
            //This will sort the dates in ascending order (earlier dates first)
            return [date1 compare:date2];
            //Use [date2 compare:date1] if you want an descending order
        }];

        self.releaseWeekDictionary = [NSDictionary dictionaryWithDictionary:tmpDict];
        self.releaseWeek = [NSArray arrayWithArray:unsortedReleaseWeek];

        [self.collectionView reloadData];

    } failure:nil];

Edit: In your code, you were no longer assigning anything to upcomingReleases (block of commented code lines 91-102), and you were crashing referencing the index that didn't exist in the array. The fix is easy:

109: self.upcomingReleases = [dataDictionary objectForKey:@"upcoming_releases"];

122: for (NSDictionary *upcomingReleaseDictionary in self.upcomingReleases) {
Erik Kerber
  • 5,646
  • 7
  • 38
  • 56
  • The app load but then it crashes, the error points to my cellForItemAtIndexPath: NSDictionary *upcomingReleaseDictionary = [self.upcomingReleases objectAtIndex:indexPath.row]; My whole project is on github just incase you want to look at my entire code: https://github.com/chrisbedoya/solearchives. Thanks for your help. – ChrisBedoya Nov 22 '13 at 02:29
  • 1.) git add . 2.) git push origin master :) You don't have anything but an empty project. @ChrisBedoya – Erik Kerber Nov 22 '13 at 03:00
  • Now you can see my app on Github (check "UpcomingReleasesViewController" under the "Releases" folder. Sorry for the inconvenience, and thanks once again. – ChrisBedoya Nov 22 '13 at 14:31
  • @ChrisBedoya See edit. You could probably have found this exact issue much more easily by turning on exception breakpoints. See this question: http://stackoverflow.com/questions/4961770/run-stop-on-objective-c-exception-in-xcode-4 – Erik Kerber Nov 22 '13 at 14:42
  • Now I have the ReusableViews showing correctly with the right date and the correct number of Cells. But for some reason the Cells keep going back to the first one every time there's a new ReusableView. If there's two cells in one view my app shows the first two cells (the ones with the earliest release dates). In the next reusable view instead of showing the cells that belong to that date it shows the cells with the earliest dates once again. – ChrisBedoya Nov 22 '13 at 17:00
  • I think the problem is with my cellForItemAtIndexPath. This is what I have now which was part of my new code AFNetworking (NSDictionary *upcomingReleaseDictionary = [self.upcomingReleases objectAtIndex:indexPath.row];). This is what I had this in my old code (UpcomingRelease *upcomingRelease = [self.releaseWeekDictionary objectForKey:self.releaseWeek[indexPath.section]][indexPath.row];) – ChrisBedoya Nov 22 '13 at 17:01