cellForItemAtIndexPath being called beyond bounds - ios

I have a UICollectionView which utilizes a custom UICollectionViewCell class. The basic idea is the collection view will traverse directories and display photos contained within the directory (results via API calls). The initial view controller displays a UICollectionView, and taps on the cells generates a new view with a new UICollectionView. The directory contents are stored in an NSMutableDictionary with the directory ID being the key.
The problem occurs when a new view/collectionview is created with a rather short list of photos -- usually less than 30. When I rotate the device, the collection view attempts to re-render, and the app crashes with a "index X beyond bounds [0 .. Y]", with X being a number larger than Y (the last element of the array).
I've noticed this tends to only happen when the newly displayed collection view has less items than the "root" collection view.
Here are examples of the relevant (some of it, anyway) code:
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
return [[photos objectForKey:api.directoryID] count];
}
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView {
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
PhotoCell *photoCell = [cv dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
photoCell.photo = [[photos objectForKey:api.directoryID] objectAtIndex:indexPath.row];
return photoCell;
}
At the very least, I'd like to do a try/catch on the cellForItemAtIndexPath, but don't know where I would put the try/catch. Perhaps in a custom UICollectionView?
Thanks!

The problem is here:
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
return [[photos objectForKey:api.directoryID] count];
}
I assume, you use one delegate and dataSource object for all of your UICollectionView instances.
If so, rewrite your code in this manner:
- (NSInteger)collectionView:(UICollectionView *)view
numberOfItemsInSection:(NSInteger)section
{
if ( view == _firstCollectionView )
{
return [[photos objectForKey:api.directoryID] count];
}
else if ( view == _secondCollectionView )
{
return ...;
}
}
Also you should rewrite collectionView:cellForItemAtIndexPath: method in such way.

Related

How to customise UICollectionView Cell to show image grid as shown in attachment?

I want to show images as shown in the attachment. How can I do this using storyboard and objective c?
Based on the image count, the cell size needs to be adjusted.
EDIT
This answer applies to the original question with a single layout based on a large first image and smaller latter images.
This is really easy ... I did not bother with iOS versions and what I give below needs a lot of additional work, but at least it will give you the idea.
The storyboard
In the storyboard I really just set up two items in the collection view. One large and one small picture. You could easily do it with one, here I use two as then I can load the image in storyboard and need not bother with configuring the cells.
Hopefully the image below will help you understand what I mean. Also note the horizontal scrolling.
The collection view
This is bare bones. Of course it needs work. Especially I do NO cell configuration but dequeue and return the cell straight without any configuration. You need to configure each cell by loading the image that should be displayed for it.
The two cell types I define in storyboard are called imgLarge and imgSmall. Note I use the same as item identifier as well.
The most important thing here, that really makes it work, is the last message, where I return two different sizes for the two different cells.
#import "TestCollectionViewController.h"
#interface TestCollectionViewController () < UICollectionViewDataSource, UICollectionViewDelegateFlowLayout >
#end
#implementation TestCollectionViewController
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
self.collectionView.collectionViewLayout.invalidateLayout;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
}
#pragma mark <UICollectionViewDataSource>
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 10;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if ( indexPath.row )
{
return [collectionView dequeueReusableCellWithReuseIdentifier:#"imgSmall" forIndexPath:indexPath];
}
else
{
return [collectionView dequeueReusableCellWithReuseIdentifier:#"imgLarge" forIndexPath:indexPath];
}
}
#pragma mark <UICollectionViewDelegate>
- ( CGSize ) collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat h = collectionView.bounds.size.height;
if ( indexPath.row )
{
return CGSizeMake( h / 2, h / 2 );
}
else
{
return CGSizeMake ( h, h );
}
}
#end
This is actually the second version. The first version just used constants here. Since they are calculated, to make it work, you need to set all the spacing and insets to 0 in storyboard.
The result
Voila!
I mentioned that this is the second version. The previous one looked slightly different as you can see below. I kept the image, for the code of the earlier one just look at the history of this post.

iOS cellForItemAtIndexPath beyond the Array bound

A question about UICollectionView. UICollectionView nested in UITableViewCell. The problem is the following simple code, but online [self.imageInfos objectAtIndex:indexPath.row] here often appear array beyond bound, very strange!! IndexPath bound is not above the collectionView: (UICollectionView *) collectionView numberOfItemsInSection: (NSInteger) section this function constraints, why the array beyond bound.
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
return self.imageInfos.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
LKMImageInfo *imageInfo = [self.imageInfos objectAtIndex:indexPath.row];
return imageCell;
}
Your UICollectionView item represents LKMImageInfo object. So instead of fetching LKMImageInfo object from indexPath.row, you should fetch it from indexPath.item. Like below:
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
return self.imageInfos.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
LKMImageInfo *imageInfo = [self.imageInfos objectAtIndex:indexPath.item];
return imageCell;
}
Use custom table view cell and inside add collection view. Write collection view delegates in the custom class of table view cell.

CollectionView dequeCell with removing subviews from cell makes view appear slow

I have code:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 30;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:REUSE_IDENTIFIER forIndexPath:indexPath];
[[cell subviews]
makeObjectsPerformSelector:#selector(removeFromSuperview)];
[cell addSubview:someView];
return cell;
}
Above code gets executed in viewWillAppear
If I make the above code executed 30 times, the view appearance is really slow.
Due to cell reuse, I need to make a call to removeFromSuperView. Otherwise stale wrong data is displayed on the cell.
I am not able to figure out what could be causing the slowness??
Can you provide hints?
Your appearance is slow because you are iterating through all the subviews within the cell and removing from superview. This is a very expensive process and should not be done in the cellForItemAtIndexPath method of the collectionView data source/ Infact this should never be done. If you need to display relevant content you need to access the instance of the cell and update the properties of the UI elements within the cell.

Using a CollectionView to add a tile effect when the user types

I have a test application I have made for a part of my workflow. What I am trying to achieve is a fancy way of showing the user what they are typing for a Word styled game.
At the moment this is the approach but there could be an easier/better route. I have a UITextField which is not shown to the user and the keyboard is shown on viewDidLoad. What I am trying to have happen is each time a letter is pressed on the keyboard a new Tile showing the letter capitalised is added to the screen area above i.e. 'W', then another letter would mean another tile added i.e. 'I' next to the previous...
I have setup a UICollectionView and custom cell with a label in, that is all. The VC is the dataSource of the UICollectionView. The UITextField also has its delegate set to the self (the VC).
I cannot work out how to have the tiles (cells) created each letter.
#pragma mark -
#pragma mark - Key board delegate methods
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSLog(#"%s",__PRETTY_FUNCTION__);
NSString *lastLetterTyped = [textField.text substringFromIndex:[textField.text length] - 1];
[self.wordArray addObject:lastLetterTyped];
[self.tileCollectionView reloadData];
return YES;
}
#pragma mark -
#pragma mark - Collection View Data Source Methods
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 3;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// we're going to use a custom UICollectionViewCell, which will hold an image and its label
//
WordCVCell *cell = [cv dequeueReusableCellWithReuseIdentifier:kCellID forIndexPath:indexPath];
// make the cell's title the actual NSIndexPath value
NSString *lastLetter = [self.typedWord substringFromIndex:[self.typedWord length] - 1];
cell.label.text = lastLetter;
return cell;
}
Your
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 3;
}
Should read as
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
[self.wordArray count];
}
This will trigger the following the same number of times as there are objects in your array.
-(UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// we're going to use a custom UICollectionViewCell, which will hold an image and its label
//
WordCVCell *cell = [cv dequeueReusableCellWithReuseIdentifier:kCellID forIndexPath:indexPath];
// make the cell's title the actual NSIndexPath value
NSString *lastLetter = [self.wordArray objectAtIndex:indexPath.row];
cell.label.text = lastLetter;
return cell;
}
You need to have a NSMutableArray that you will add to each time the user types a character. For that you need to hook up your controller to UITextfieldDelegate. After that each time you add to this array you need to call [collectionView reloadData] and your number of items will be [myMutableCharacterArray count];
So basically each time a user types a letter, add it to a mutable array and call [collectionView reload data] to refresh the collectionview.

iOS 6 CollectionView Dynamically change Layout

I'm kinda newbie in UICollectionView and I'm working in a project that dynamically changes the UICollectionViewLayout in a given action.
My CollectionView has 6 sections, each of them with 10 elements. Those Cells are basically an UIImageView and my Custom Layout called StackViewLayout stacks all elements for each section (something like Apple's Photos.app).
If the user selects the element of the stack (for all sections), the UICollectionView dynamically changes the layout to the UICollectionViewFlowLayout, so all the elements can be viewed as grid.
My problem is that when user selects a stack, no matter which section, when the Layout is changed do Flow Layout, all sections are displayed in the grid, instead of displaying the elements for the selected section (stack), which is the behavior I wanted.
Is there any way to show only the Flow Layout for the selected section in the Custom Layout?
Here is my Controller Implementation snippet code:
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadStackLayout]; // stack all sections at first load.
}
#pragma mark - UICollectionView Data Source Methods
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 6;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 10;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
{
// custom UICollectionViewCell, which will hold an image.
CVCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MyCell" forIndexPath:indexPath];
cell.imageView.image = _images[indexPath.row];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
// element size.
return CGSizeMake(100,100);
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (isStacked)
{
[self loadFlowLayout]; //HERE I WANT TO LOAD FLOW LAYOUT ONLY FOR THE SECTION OF THE SELECTED ITEM!
} else
{
// to implement.
}
}
// HERE IS THE METHOD THAT CALLS MY CUSTOM LAYOUT IN ORDER TO STACK THE ELEMENTS FOR EACH SECTION IN COLLECTION VIEW. IT IS WORKING AS IT SHOULD.
-(void)loadStackLayout
{
if (([self.collectionView.collectionViewLayout isKindOfClass:[UICollectionViewFlowLayout class]]))
{
isStacked = YES;
[self.collectionView setCollectionViewLayout:[[StackViewLayout alloc] init] animated:YES];
}
}
// HERE IS THE METHOD THAT CALLS MY FLOWLAYOUT IN ORDER TO UN-STACK THE ELEMENTS AND SHOW THEM IN A GRID. CURRENTLY IT IS SHOWING ALL SECTIONS IN THE GRID.
-(void)loadFlowLayout
{
if (([self.collectionView.collectionViewLayout isKindOfClass:[StackViewLayout class]]))
{
isStacked = NO;
[self.collectionView setCollectionViewLayout:[[UICollectionViewFlowLayout alloc] init] animated:YES];
}
}
I think you can probably do it by having an if clause in your numberOfItemsInSection: method, to return 0 for any section that's not the selected one. Obviously, you'll need to keep track of the selected section to do this.

Resources