3

Thanks in advance for checking out this project and lending me a hand.

Storyboard Connections

I am trying to display an array of UIImages in my Collection View. I have connected everything in the storyboard.
My ViewController is both the delegate and dataSource for my Collection View.
My cell contains a UIImageView that has an IBOutlet in my custom cell's header file.

How the App Currently Works

  1. The App creates an array of images from the wikipedia API based on a search by a user.
  2. Once the user types in his or her search, the ViewController passes the NSString off to the model (GetImageURL.h and GetImageURL.m.)
  3. This seems to be working based off the NSLogs that I am using.
  4. The Model passes it back to the controller once it has set up an array of UIImages.
  5. The ViewController then attempts to reload the collectionView's data and the app crashes.

The error message looks like this:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter'
*** First throw call stack: 
(0x1c97012 0x10d4e7e 0x1c96deb 0x518748 0x533b1b 0x533646 0xf9ff8 0xfa232 0x5338d5 0x29a4 0x3ef6 0xb13fb4 0xb13e67 0x30ca 0x49fb53f 0x4a0d014 0x49fd7d5 0x1c3daf5 0x1c3cf44 0x1c3ce1b 0x1bf17e3 0x1bf1668 0x18ffc 0x1b9d 0x1ac5)
libc++abi.dylib: terminate called throwing an exception


My Question

I am wondering what I am doing wrong to create collectionView that causes it to crash upon a call later to reloadData.

Code

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UICollectionViewController <UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UIApplicationDelegate>

//receives a mutable array of UIImage(s)
-(void)receiveArrayOfImages: (NSMutableArray *)arrayOfImages;
@end


ViewController.m

#import "ViewController.h"
#import "Cell.h"
#import "GetImageURL.h"

NSString *kCellID = @"cellID";

@interface ViewController () {
    UISearchBar *searchBar;
}
@property (nonatomic, strong) NSString *searchBarText;
@property (strong, nonatomic) NSMutableArray *arrayOfImages;

@end

@implementation ViewController

-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    if ([self.arrayOfImages count] > 0) {
        NSLog(@"return %d items for collection view", [self.arrayOfImages count]);
        return [self.arrayOfImages count];
    } else {
        NSLog(@"returned 0 items for collection view");
        return 0;
    }
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {

    NSLog(@"cellForItemAtIndexPath");

    Cell *cell = [cv dequeueReusableCellWithReuseIdentifier:kCellID forIndexPath:indexPath];
    int row = [indexPath row];
    cell.cellImage.image = [self.arrayOfImages objectAtIndex:row];
    return cell;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    //Creates a Search Bar Button System Item.
    UIBarButtonItem *searchButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(tapSearchButton:)];
    self.navigationItem.rightBarButtonItem = searchButton;
}


-(void)tapSearchButton:(id)sender {
    //Adds or takes away searchbar on navigationbar.
    if (!searchBar) {
        searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(-5.0, 0.0, 320.0, 44.0)];
        searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        UIView *searchBarView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 310.0, 44.0)];
        searchBarView.autoresizingMask = 0;
        searchBar.delegate = self; //this line causes a warning. "Assigning to 'id<UISearchBarDelegate> from incompatible type 'ViewController *cons_strong"
        [searchBarView addSubview:searchBar];
        self.navigationItem.titleView = searchBarView;
        [searchBar becomeFirstResponder];
    } else {
        self.navigationItem.titleView = nil;
        searchBar = nil;
    }
}

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
    GetImageURL *bookName = [[GetImageURL alloc] init];
    bookName.bookName = [searchBar text]; //this line causes a warning. "Local declaration of 'searchBar' hides instance variable"
    NSLog(@"In ViewController, the book name is: %@", bookName.bookName);
    [bookName recieveBookUrl:bookName.bookName];
    [self.view endEditing:YES];
}

-(void)receiveArrayOfImages: (NSMutableArray *)arrayOfImages {
    //receives an array of UImages called arrayOfImages and sets it to self.arrayOfImages
    self.arrayOfImages = arrayOfImages;
    NSLog(@"This is the array of UIimages in ViewController.m: %@", self.arrayOfImages);

    [self.collectionView reloadData]; //This line causes the app the crash. "Reason: UICollectionView must be initialized with a non-nil layout parameter"
}

@end


Cell.h

#import <UIKit/UIKit.h>

@interface Cell : UICollectionViewCell

//UIImageView inside Custom Cell
@property (strong, nonatomic) IBOutlet UIImageView *cellImage;

@end


Cell.m

#import "Cell.h"

@implementation Cell

- (id)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
    }
    return self;
}

@end


GetImageURL.h

#import <Foundation/Foundation.h>
#import "ViewController.h"

@interface GetImageURL : NSObject

@property (strong, nonatomic) NSString *bookName;
@property (strong, nonatomic) NSMutableArray *imageArray;

-(void)recieveBookUrl:(NSString *)bookName; //recieves a string from the viewController that the user inputs

@end


GetImageURL.m

#import "GetImageURL.h"

@interface GetImageURL()

@property (strong, nonatomic) NSString *pageID;
@property (strong, nonatomic) NSMutableArray *imageUrlArray;

@end


@implementation GetImageURL

//takes the string that the user entered and alters it to fit the wikipedia API format
-(void)recieveBookUrl:(NSString *)bookName {
    self.imageUrlArray = [[NSMutableArray alloc] init];
    self.imageArray = [[NSMutableArray alloc] init];
    bookName = [bookName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; //or use string by replacing @" " with @"%20";
    NSString *wikiAPI  = @"http://en.wikipedia.org/w/api.php?format=json&indexpageids&action=query&prop=images&titles=";
    bookName = [wikiAPI stringByAppendingString:bookName];
    NSURL *bookURL = [NSURL URLWithString:bookName];
    //Plan to put this action in the background later
    dispatch_async(dispatch_get_main_queue(), ^() {
        NSData *data = [NSData dataWithContentsOfURL:bookURL];
        [self performSelectorOnMainThread:@selector(getBookTitleApi:) withObject:data waitUntilDone:YES];
    });
}

//first part of the API sucessfully gets the padeID and all the images returned by the API.
-(void)getBookTitleApi: (NSData *)data {
    NSError *error;
    NSDictionary *json = [NSJSONSerialization
                          JSONObjectWithData:data
                          options:kNilOptions
                          error:&error];
    NSDictionary *parseQueryBookTitle = [json objectForKey:@"query"];
    NSArray *parsePageIdsBookTitle = [parseQueryBookTitle objectForKey:@"pageids"];
    for (NSDictionary *pageIdBookTitle in parsePageIdsBookTitle) {
        self.pageID = [NSString stringWithFormat:@"%@", pageIdBookTitle];
    }

    NSDictionary *parsePagesBookTitle = [parseQueryBookTitle objectForKey:@"pages"];
    NSDictionary *parsePageIdBookTitle = [parsePagesBookTitle objectForKey:self.pageID];
    NSDictionary *parseImagesBookTitle = [parsePageIdBookTitle objectForKey:@"images"];
    NSDictionary *valueTitleBookTitle = [parseImagesBookTitle valueForKey:@"title"];

    for (NSDictionary *imageFilenames in valueTitleBookTitle) {
        NSString *imageFilename = [NSString stringWithFormat:@"%@", imageFilenames];
        imageFilename = [imageFilename substringFromIndex:5];
        imageFilename = [imageFilename stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; //or use string by replacing @" " with @"%20";

        NSString *wikiImageAPI = @"http://en.wikipedia.org/w/api.php?action=query&format=json&indexpageids&prop=imageinfo&iiprop=url&titles=Image:";
        imageFilename = [wikiImageAPI stringByAppendingString:imageFilename];

        NSURL *imageFilenameUrl = [NSURL URLWithString:imageFilename];
        NSData *data = [NSData dataWithContentsOfURL:imageFilenameUrl];
        [self performSelectorOnMainThread:@selector(getImageApi:) withObject:data waitUntilDone:YES];
    }
    //Uses the URLs to create an NSData object and then uses that data to create a UIImage which is added to a Mutable Array
    for (NSString *jpgURL in self.imageUrlArray) {
        NSURL *imageURL = [NSURL URLWithString:jpgURL];
        NSLog(@"This is an imageURL in GetImageURL.m: %@", imageURL);
        NSData *data = [NSData dataWithContentsOfURL:imageURL];
        UIImage *image = [UIImage imageWithData:data];
        [self.imageArray addObject:image];
    }


    NSLog(@"The array of images in GetImageURL.m contains %d images called %@", [self.imageArray count], self.imageArray);
    ViewController *view = [[ViewController alloc] init];
    [view receiveArrayOfImages:self.imageArray]; //sends the array of images to the View Controller

    //objects removed from array so that next user search can start over
    [self.imageArray removeAllObjects];
    [self.imageUrlArray removeAllObjects];
}

//Uses the image filenames from the first section and gets the specific URLS for the image. Limits results to .jpg images
-(void)getImageApi: (NSData *)data {
    NSError *error;
    NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];

    NSDictionary *parseQueryImage = [json objectForKey:@"query"];
    NSDictionary *parsePageIdsImage = [parseQueryImage objectForKey:@"pageids"];

    for (NSString *pageIdImage in parsePageIdsImage) {
        NSDictionary *parsePagesImage = [parseQueryImage objectForKey:@"pages"];
        NSDictionary *parsePageIdImage = [parsePagesImage objectForKey:pageIdImage];
        NSDictionary *parseImageInfoImage = [parsePageIdImage objectForKey:@"imageinfo"];
        NSDictionary *valueImageInfoUrlsImage = [parseImageInfoImage valueForKey:@"url"];

        for (NSString *valueImageInfoUrlImage in valueImageInfoUrlsImage) {

            if ([valueImageInfoUrlImage hasSuffix:@".jpg"]) {
                [self.imageUrlArray addObject:valueImageInfoUrlImage];
            }
        }
    }
}

@end


Sorry for dumping all the code in here. I figured it would be easier if it was all there. Maybe it will be helpful to someone else too.

Thanks!

Shekhar Gupta
  • 6,056
  • 3
  • 27
  • 49
Daniel
  • 31
  • 1
  • 4

3 Answers3

0

Do you have your layout set to flow? It should look like this in your Storyboard:

Collection view layout screenshot

If you set it to custom you need to create a layout yourself.

Patrick Tescher
  • 3,226
  • 1
  • 15
  • 30
  • I have not changed any settings except assigning the correct class for my cell and changing it's reuseID. I appreciate your help! – Daniel Aug 01 '13 at 19:42
0

If your collection view cell isn't created in a storyboard then I think you have to register it.

If your cell was programmed

// Add this in your controller's viewDidLoad method
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:kCellID];

If your cell has a nib file, I think it is registerNib:forCellWithReuseIdentifier:

Matt Tang
  • 1,257
  • 11
  • 16
  • Thanks for your comment. I am using the prototype cell that I had read that somewhere and tried is included with the Collection View Controller in the storyboard. Would I be better off making the cell programmatically? I had found the registerClass: in Apple's documentation, but it was not helping so I took it out. Any other ideas? Thanks again for your help. – Daniel Aug 02 '13 at 22:44
-1
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return array.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{

    static NSString *identifier = @"Cell";
    UIImage *smallimage = [UIImage imageNamed:[NSString StringWithFormat:@"%@.png"[array[indexpath]]];

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

    UIImageView *gridimageview = (UIImageView *)[cell viewWithTag:100];
    [gridimageview setImage:smallimage];
    return cell;

}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    NSLog(@"index path is %d", indexPath.row);
   //Enter the action you want when an image is clicked in collection view
    }
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
//    Write your code here to  deselect the selected image
    }
}
ArunMak
  • 398
  • 2
  • 12
  • Thanks for your reply. I am having a bit of trouble with your code, though I do agree that the cellForItemAtIndexPath: is probably my issue. I am getting an error on your creation of the UIImage *small image as well as the line below that. Also, do I need to create another UIImage if my array already contains UIImages? Thanks again for your help! – Daniel Aug 02 '13 at 22:40
  • Now try this code and check whether you have set a tag for your imageview inside the collectionviewcell in storyboard as 100. Also the array[indexpath] use your array name. – ArunMak Aug 05 '13 at 10:35
  • I have set the tag for the cell as 100 in the storyboard and replaced "array[indexpath] with self.arrayOfImages. Should it be self.arrayOfImages[indexPath]? I am getting an error that says the UICollectionView must be initialized with a non-nil layout parameter. – Daniel Aug 05 '13 at 15:10
  • You need to set up an identifier on your storyboard for the collection view controller or it won't return anything – ArunMak Aug 06 '13 at 05:33
  • In the identity inspector for the Collection View Controller, I have the storyboard ID and restoration ID set to collectionViewController. Do I need to use this in my code somewhere? – Daniel Aug 06 '13 at 17:00
  • Check whether you have set the datasource and delegates for UIcollectionView and also check you have set the imageview tag as 100 which is inside the uicollectioviewcell and also check whether you have set the identifier as Cell for uicollectionviewcell .This is applicable if you are using the above code – ArunMak Aug 07 '13 at 12:50