I have a simple UICollectionView up and running and have it so you can select a cell then save your selection. But guess what? I have a problem!
After scrolling the selected cell off-screen and back on, it looses its selection. Pretty simple at the surface. How can I stop this?
Is this something to do with cell reuse? Here is my code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"AircraftConfigurationCell";
SLAircraftConfigurationCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
//Customise cell...
return cell;
}
Do you have a datasource which is formed possibly from an array containing data models?
If so then you're off to a great start and can sort out this selection intermittent behaviour in no time!
In your data model .h file create a property like so property (nonatomic, retain) BOOL selectedState; and in the .m file initialise the selectedState to FALSE in your data object's init method.
Once that's done in your UICollectionView's didSelect method, set the dataObject's selectedState to YES that is associated to that cell/item index.
Then finally in your cellForItemAtIndexPath method, ensure you do an if check on the dataModel instance that youre going to set the cellItem with for the selectedState property. If its set to `NO' set the selectionStyle to none, otherwise set it to yes.
I used to have your problem before where selection would be lost when scrolling, but now its being managed and the result is persistent.
Related
I am trying to create custom UICollectionViewCell which contains few properties, and depending on values of those properties drawing components inside cell. I am using dequeueReusableCellWithReuseIdentifier for creating cell, then I am setting some properties, and at the end calling layoutIfNeeded function which is overridden inside my custom cell. Overridden function is setting some properties of cell also for example BOOL property is set to YES, and after refreshing cell (calling reloadData on collection view) function layoutIfNeeded is called again. When I try to read my BOOL property which is set to YES, i am always getting default value which is NO for the first time i call reloadData. When I call reloadData second time, property is set to YES. Any idea what am I doing wrong? Here is code I am using:
on button click I am calling:
[myCollectionView reloadData];
method cellForItemAtIndexPath looks like:
MyCustomCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"myCustomCell" forIndexPath: indexPath];
cell.device = [collectionArray objectAtIndex:indexPath.row];
[cell layoutIfNeeded];
return cell;
And code of layoutIfNeeded inside MyCustomCollectionCell.m
-(void)layoutIfNeeded{
NSLog(#"bool prop: %d",changedStatus);
changedStatus = YES;
}
BOOL property is defined in MyCustomCollectionCell.h :
#property (nonatomic, assign)BOOL changedStatus;
UPDATE:
I am sorry, I made a mistake in my post. I am not refreshing collection with reloadData, but with reloadItemsAtIndexPaths; This call causes init method of my custom cell to be called again (not just when collection view is loaded for the first time) and after that layoutIfNeeded. I thing problem is that cell is not reused, but created again, causing all properties to disappear. Any idea how to fix this?
You can't use cells to store state data. Cells get used, put in the reuse queue, and then recycled. The specific cell object that stores the data for a particular indexPath may change when the table is reloaded, when a cell is reloaded, when you scroll to expose new cells, etc.
Save state data in your data model.
What is the purpose of changedStatus property ?
Try setting changedStatus = YES; in layoutSubviews instead :
- (void)layoutSubviews
{
[super layoutSubviews];
self.changedStatus = YES;
}
Set this changedStatus Bool inside your ViewController instead of UICollectionViewCell.
BOOL property is defined in YourViewController.h :
#property (nonatomic, assign)BOOL changedStatus;
When you you want to refresh the CollectionView
[myCollectionView reloadData];
changedStatus = YES;
Then inside your MyCustomCollectionCell.m create a new method like,
-(void)customLayoutIfNeeded : (BOOL)status{
NSLog(#"bool prop: %d",changedStatus);
changedStatus = YES;
}
Add this to MyCustomCollectionCell.h as well.
Then inside cellForItemAtIndexPath do this,
MyCustomCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"myCustomCell" forIndexPath: indexPath];
cell.device = [collectionArray objectAtIndex:indexPath.row];
[cell customLayoutIfNeeded: changedStatus];
return cell;
I would like to subclass a UICollectionView (not a UICollectionViewController), and I would like to know how I can set it up so that when the user highlights (or selects) a cell, the collection view can be notified, so I can perform a little animation on the cell. You may ask why I can't do that in a view controller. I chose to subclass UICollectionView so that it could be reusable. I am relatively new to iOS programming, and I would welcome any suggestions or ideas.
You can use a block ^{}
Create a class with a .xib file. The .xib file will be used for each cell.
In your .xib file, add a clear UIButton, so that is on top of all your subviews. So the user can click on it.
In your .h file add
#property (copy, nonatomic) void (^actionBlock)(void);
In your .m file add and link it to your UIButton in the .xib file
- (IBAction)showAnimation:(id)sender
{
if (self.actionBlock) {
self.actionBlock();
}
}
Now in UICollectionViewController, cellForItemAtIndexPath, call the block
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCellClass *cell =
[collectionView dequeueReusableCellWithReuseIdentifier:#"cell"
forIndexPath:indexPath];
cell.actionBlock = ^{
//Here you have access to indexPath.section and indexPath.row
NSLog(#"Going to animate the cell %# x %#", indexPath.section, indexPath.row);
//do any other code for this specific cell
};
return cell;
}
Using action blocks, is like opening a portal into each cell, happy coding
UICollectionView is the view and UICollectionViewController is the viewController.
The UICollectionView you are subclassing is use for updating user interface with viewController's core logic to be triggered while highlighting (or selecting) the cell.
So you should set up the selected logic in your viewController.
If you understood Delegate Pattern, the normal way to update your UICollectionView's cell while selected is using a delegate. When something triggered, call viewcontroller to do that for you.
Check out these links about
Cocoa MVC Design Pattern and Designing Your Data Source and Delegate for CollectionView.
In my project I'm creating custom cells by subclassing UITableViewCell. When cellForRowAtIndexPath: is fired I do a pretty basic stuff like:
MyCustomCell *cell = [self.tableView dequeueReusableCellWithIdentifier:[MyCustomCell identifier]];
I don't want to manually configure cell properties in cellForRowAtIndexPath: so I thought I'd create a method inside MyCustomCell called configureWithModel: which is filling MyCustomCell with proper data. So far, so good! Now inside cellForRowAtIndexPath: I have something like:
MyCustomCell *cell = [self.tableView dequeueReusableCellWithIdentifier:[MyCustomCell identifier]];
[cell configureWithModel:model];
In configureWithModel: I assign some data (image also) to cell so as you'd guess it could be slow'n'heavy so I wonder if this is a good solution to have a method like this in subclass of MyCustomCell? What is more, how it's related to prepareForReuse?
Doing this [cell configureWithModel:model]; is the best approach because take for a case when you want to use configureWithModel: in more than 2 tableViews you can avoid code redundancy and cell level control would be there with cell itself.
Use of [cell configureWithModel:model]; will make your code look like more structured, but for image use the following delegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
Example :
- (void)tableView:(UITableView *)tableView willDisplayCell:(AlbumCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
AlbumBO *album = [self.arrAlbums objectAtIndex:indexPath.row];
dispatch_async(imageQueue_, ^{
UIImage *image = [self retrieveImageWithImageUrl:album.coverPhoto];
dispatch_async(dispatch_get_main_queue(), ^{
[cell.imgVwAlbum setImage:image];
});
});
}
Here
AlbumCell is my Custom table cell
AlbumBO is the object for containing image object
And
[self retrieveImageWithImageUrl:album.coverPhoto]
is the user defined method to download image.
This sounds like a fairly decent usage of the singular responsibility principle. Where this might bite you is if your cells need to be binded with images that must be downloaded from a server. In this instance you don't want your cell responsible for triggering a download since the cell will then also be responsible for monitoring the progress of the download. Since these cells are reusable this becomes more problematic as the cell becomes reused.
So yes, in a simple case where you need to bind data to a cell it makes sense for the cell to be responsible for configuring its subviews with the relevant data.
Regarding prepareForReuse a casual glance at the documentation details
Discussion If a UITableViewCell object is reusable—that is, it has a
reuse identifier—this method is invoked just before the object is
returned from the UITableView method
dequeueReusableCellWithIdentifier:. For performance reasons, you
should only reset attributes of the cell that are not related to
content, for example, alpha, editing, and selection state. The table
view's delegate in tableView:cellForRowAtIndexPath: should always
reset all content when reusing a cell. If the cell object does not
have an associated reuse identifier, this method is not called. If you
override this method, you must be sure to invoke the superclass
implementation.
I am using a static cell layout in a UITableView. During the workflow I need to address the attributes of a specific cell. All cells have an identifier but i did not found a way to actually address the cell using the identifier.
Thanks,
em
The reason why cellForRowAtIndexPath: is not reliable is probably this (from the documentation):
An object representing a cell of the table or nil if the cell is not visible or indexPath is out of range.
If you ask for a cell that is not visible on screen, it may have been purged from the table view.
Since you mention that you use a static cell layout in your tableview (I assume you don't rely on cell reuse), you could consider keeping the cells as private properties:
In the private interface:
#interface MyViewController ()
#property (nonatomic, readonly) MyTableViewCellClass *myStaticCell;
#end
In the implementation:
#implementation MyViewController {
MyTableViewCellClass* _myStaticCell;
}
- (MyTableViewCellClass*) myStaticCell {
if (!_myStaticCell) {
// Initialize _myStaticCell
}
return _myStaticCell
}
You can then call this lazy loaded property when tableView:cellForItemAtIndexPath:is called and whenever you need to modify it.
Note that this approach is only recommended if you have a tableview with static content and don't rely on cell reuse.
Here indexPathSelected is the selected specific cell
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:indexPathSelected inSection:0]];
indexPathSelected = indexPath.row;
}
I have implemented an UICollectionView image gallery. Each cell has some views and I would like to hide or show that views when I change the current cell, at least when the event starts. is there any method? or should I do something by delegate? I have paging enabled and custom cell and FlowLayout.
I have done everything almost like this tutorial
One way would be to keep the current selected cell index in a local variable in your viewController, and use that index to perform any actions when selecting another cell:
#property (nonatomic) NSIndexPath *selectedCellIndexPath;
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (![self.selectedCellIndexPath isEqual:indexPath]) {
UICollectionViewCell *lastSelectedCell = [collectionView cellForItemAtIndexPath:selectedIndexPath];
// Perform any change to lastSelectedCell before deselecting it
[collectionView deselectItemAtIndexPath:lastSelectedIndexPath animated:YES];
}
self.selectedCellIndexPath = indexPath;
UICollectionViewCell *selectedCell = [collectionView cellForItemAtIndexPath:indexPath];
// Change what you want in the newly selected cell;
}