iOS 6 CollectionView Dynamically change Layout - ios

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.

Related

How to delay load specific content of UICollectionViewCell

I implemented a calendar view with UICollectionView, when scrolling the calendar view very fast, it's not smooth. So I'm thinking whether I can load static content of each cell firstly, and then refresh once specific content has been loaded. So how to delay loading specific contents of each UICollectionViewCell
Specifically, in below function, I'll construct each UICollectionViewCell and return it. Now I just want to construct static contents (such as the date), and delay loading specific contents (such as the background color, if I have an event this day, I'll change the background of this cell), so where should I load specific contents, and how to only refresh showing cell
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:UICollectionViewCellIdentifier
forIndexPath:indexPath];
NSDate *date = [self dateAtIndexPath:indexPath];
cell.dateLabel.text = [date description];
// This is the part I want to delay, since it's cost.
if (dataModel.hasEventAtDate(date)) {
cell.dateLabel.backgroundColor = [UIColor blue];
}
return cell;
}
You may need have an instance variable to track if cells need to update:
Boolean cellNeedsUpdate = NO
In cellForItemAtIndexPath, check if you need to fully update the cells:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if (cellNeedsUpdate) {
// fully update the cell
} else {
// do partial update
}
}
Track end scrolling of the collectionView, then reload the collectionView:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
cellNeesUpdate = !cellNeedsUpdate;
[collectionView reloadData];
}

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.

Hide UICollectionView items in certain layouts

I have a UICollectionView that uses a couple of different layouts depending on user preferences. I want certain cells to show up for some layouts but not for others. How do I accomplish this? Do I actually need to reload the collection view data?
There are two steps to accomplishing this (which also works with UITableView).
Update the data source. If you're using an NSDictionary or NSArray, then you'll need to add or remove the items you want to show/hide.
Call reloadData on the UICollectionView or UITableView. That's pretty much it.
If you want to remove or add with an animation, that's different. There are a couple more methods in the middle that you have to call and make sure that your update sequence is correct. But that is a different question altogether.
EDIT:
As an example of how to use an array
- (void)methodCalledWhenLayoutChanges:(BOOL)includeOptionalString {
if (includeOptionalString) {
[_collectionViewDataSourceArray addObject:_optionalString];
} else {
[_collectionViewDataSourceArray removeObject:_optionalString];
}
[self.collectionView reloadData];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return _collectionViewDataSourceArray.count;
}
// Never actually setup a collection view like this. This is just an example of how to reference a data source for creating a collection view cell.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [[UICollectionViewCell alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 50.0f, 50.0f)];
UILabel *textLabel = [[UILabel alloc] initWithFrame:cell.bounds];
textLabel.text = [_collectionViewDataSourceArray objectAtIndex:indexPath.row];
[cell addSubview:textLabel];
return 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.

cellForItemAtIndexPath being called beyond bounds

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.

Resources