I have a UICollectionView with labels inside the cells that change automatically periodically. When this update triggers I call reloadData on the UICollectionView and I have set the cells to change the background colour on [UICollectionViewCell setHighlighted:].
The problem is if a user holds down on a cell then the update happens, when the user releases the cell stays highlighted and also cannot be selected anymore.
I have noticed that dequeueReusableCellWithReuseIdentifier:forIndexPath: calls setHighlighted on the cells after reloadData.
I have also tried reloadSections: instead of reloadData, this fixes the problem of the cells getting 'stuck', but causes a fade out and in on the cells when ever its called.
Placing the calls inside performBatchUpdates: doesn't seem to fix the problem either.
Inside the cell's class try calling:
- (void)prepareForReuse {
[super prepareForReuse];
[self setHighlighted:NO];
... Any other custom stuff that should be cleaned up ...
}
The issue is that you are probably doing background coloring differently then the cell would normally do on its self when highlighted. Normally the cell's superclass would undo those changes in prepareForReuse, but it doesn't know about your changes.
I used the following as a workaround:
// Both highlightedData and lastHighlightedData are only needed if you want to prevent user from selecting a cell which data changed during the reload. If not needed, a boolean may be used instead
#property (nonatomic) id highlightedData;
#property (nonatomic) id lastHighlightedData;
#property (nonatomic) BOOL pendingCollectionViewReload;
// Wrap the reloadData call. Enqueue it if there's a highlighted cell:
- (void)reloadCollectionView
{
if (self.highlightedData) {
self.pendingCollectionViewReload = YES;
return;
}
[self.collectionView reloadData];
self.pendingCollectionViewReload = NO;
}
// When a cell is highlighted, save its index, or better the related data:
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
// Save data at indexPath to self.highlightedData
}
// Then reload the data when the cell is unhighlighted:
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
self.lastHighlightedData = self.highlightedData;
self.highlightedData = nil;
if (self.pendingCollectionViewReload) {
[self reloadCollectionView];
}
}
// The following can be used from shouldPerformSegueWithIdentifier or didSelectItemAtIndexPath to prevent actions if the data associated with the indexPath changed:
- (BOOL)selectedDataEquals:(id)data
{
// I used lastHighlightedData in addition to highlightedData because this function may be used after the didUnhighlightItemAtIndexPath was called:
return (self.highlightedData && self.highlightedData == data) || (!self.highlightedData && self.lastHighlightedData == data);
}
I encountered the same problem in a collection view that didn't need highlighting and I noticed that the didHighlight/didUnhighlight methods behaved correctly, so I ended up blocking reloads while a touch was in progress, using something like this:
BOOL blockColviewUpdate,colviewUpdateQueued;
and then
-(void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
blockColviewUpdate=YES;
}
-(void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
blockColviewUpdate=NO;
if(colviewUpdateQueued==YES) [self CollectionViewRefresh];
}
while using an own function instead of calling reloadData directly:
-(void)CollectionViewRefresh
{
if(blockColviewUpdate==YES) colviewUpdateQueued=YES;
else
{
colviewUpdateQueued=NO;
[self.colview reloadData];
}
}
It helped in my case and no reloads were lost.
I am using collection views in one of my recent projects and the following code works fine for me.
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath: (NSIndexPath *)indexPath
{
return YES;
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
{
return YES;
}
In the collectionviewcell subclass
#interface SalesLegendCell : UICollectionViewCell
below is the code
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:#"SalesLegendCell" owner:self options:nil];
if ([arrayOfViews count] < 1) {
return nil;
}
if (![[arrayOfViews objectAtIndex:0] isKindOfClass:[SalesLegendCell class]]) {
return nil;
}
self = [arrayOfViews objectAtIndex:0];
UIView* backgroundView = [[UIView alloc] initWithFrame:self.bounds];
backgroundView.backgroundColor = [UIColor colorWithWhite:0.85 alpha:1];
UIView* selectedBGView = [[UIView alloc] initWithFrame:self.bounds];
selectedBGView.backgroundColor = [UIColor colorWithWhite:1.0 alpha:1];
self.selectedBackgroundView = backgroundView;
self.backgroundView = selectedBGView;
}
return self;
}
Possibly unrelated, but this bit me: UIImage instances rendered on a UIViewCollectionCell in .alwaysTemplate mode get by default highlighted.
Related
I have been experiencing this event where UITableView cell contents are loaded fine on iOS 9, but not on iOS 8. I have verified this both on the simulator and on the device.
I have tried loading the data on viewDidLoad, viewWillAppear, and even in viewDidAppear but still the same. I have tried a dispatch_async to reload the the tableView but still the same.
Check the images below to see the problem I am experiencing.
However, when I tap the position for the stars, the UIView for the stars suddenly appears.
Here is my code for the UITableView controller.
#interface EvalTableViewController ()
#property (strong, nonatomic) NSDictionary *allItems;
#property (strong, nonatomic) NSArray *items;
#property (strong, nonatomic) NSMutableArray *ratings;
#end
#implementation EvalTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedRowHeight = 140.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
_allItems = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"AssessmentItems" ofType:#"plist"]];
_items = nil;
_items = [_allItems objectForKey:_currentEval];
[self loadRatings];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"itemCellId";
ItemTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
cell.item.text = _items[indexPath.row];
cell.ratingView.value = ((NSNumber *)_ratings[indexPath.row]).floatValue;
[cell bringSubviewToFront:cell.ratingView];
[cell.ratingView addTarget:self action:#selector(updateRating:) forControlEvents:UIControlEventValueChanged];
[cell layoutSubviews];
return cell;
}
- (void)updateRating:(HCSStarRatingView *)ratingView {
long indexOfRatingView = [self.tableView indexPathForCell:(ItemTableViewCell *)[[ratingView superview] superview]].row;
[_ratings replaceObjectAtIndex:indexOfRatingView withObject:[[NSNumber alloc] initWithFloat:ratingView.value]];
[self.tableView reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:indexOfRatingView inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
}
- (void)loadRatings {
_ratings = nil;
_ratings = [[NSMutableArray alloc] init];
for (int i = 0; i < _items.count; i++) {
[_ratings addObject:[[NSNumber alloc] initWithFloat:1.0]];
}
}
}
I have not added anything on the ItemTableViewCell code. Here it is:
#implementation ItemTableViewCell
- (void)awakeFromNib {
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
I am new on iOS app development, I hope you can help.
Thanks.
It seems that all our past efforts to solve my problem was more focused on loading the cells, and not on the UITableViewCell itself.
I have found out that the problem lies on my constraints for my label and for the star rating. I just added a height constraint to my star rating and it becomes visible when the tableView appears. I also needed to put [self.tableView reloadData]; on my viewDidAppear method.
However, the UITableViewCell displays in its fixed height initially, then adjusts it's height after the view is presented to the user which might not be appealing for the user. And the tableView scrolls when I tap the star rating.
But anyways, this gave me hope in solving my problem.
I just find it odd. Because, if it's a constraint or layout error, why does it work fine on iOS 9 and not on iOS 8?
Thanks a lot.
Try adding this code in the awakefromnib method in the tableViewCell.m file(after importing HCSStarRatingView ).Also try the debugging mode in simulator to see if the view is there before the first touch.
HCSStarRatingView *starRatingView = [[HCSStarRatingView alloc] initWithFrame:initWithFrame:CGRectMake(0, 0, self.ratingView.frame.size.width, self.ratingView.frame.size.height)];
I guess there is problem with bring subview to front may be.
please try this in cell for row at index path method .
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"itemCellId";
ItemTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
cell.item.text = _items[indexPath.row];
cell.ratingView.value = ((NSNumber *)_ratings[indexPath.row]).floatValue;
//[cell bringSubviewToFront:cell.ratingView];
[cell.ratingView addTarget:self action:#selector(updateRating:) forControlEvents:UIControlEventValueChanged];
[cell layoutSubviews];
return cell;
}
And if it is still not working than in view did load add this code and test again.
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.estimatedRowHeight = 140.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
_allItems = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"AssessmentItems" ofType:#"plist"]];
_items = nil;
_items = [_allItems objectForKey:_currentEval];
[self.tableView reloadData];
[self loadRatings];
}
Hope this will solve your problem.
After calling [self loadRatings]; , you need to reload your table data try it.
I have an UICollectionView and Custom UICollectionViewCell, where i'm loading images, when i scroll the UICollectionView, i'm seeing all the cells are refreshing, here is the code for UICollectionView delegates,
In ViewDidLoad adding this first for adding CustomCell
-(void)ViewdidLoad{
UINib *nib = [UINib nibWithNibName:#"NMCFAIPadWishListCell" bundle:nil];
[self.accountDetailsCollectionView registerNib:nib forCellWithReuseIdentifier:#"Cell"];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [[self wishListData] count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"Cell";
NMCFAIPadWishListCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
[cell setWishList:[[self wishListData] objectAtIndex:[indexPath row]] delegate:self];
return cell;
}
In setWishList method just assigning the values from the array to label and i have a button in Xib for each cell in my custom UICollectionViewCell, when user taps on that button i'm just changing the label BG color
- (void)setWishList:(NSString*)product delegate:(id)delegate
{
self.label.text = product;
}
Below is the button action
- (IBAction)editProduct:(id)sender
{
self.label.backgroundColor = [UIColor redColor];
}
Here my problem is when i scroll the Custom Cell and tap on Button in any Cell the label BG is not only changing in current cell but also in MANY CELLS.
You should not attempt to store any state in/on your cells as the cell objects themselves are reused at the discretion of the UICollectionView.
One solution to your problem could be:
In your editProduct: method (assuming your editProduct: method is in your custom UICollectionViewCell implementation), inform the collection view’s controller that the user has “selected” that product via a protocol method (or block or some other messaging mechanism).
In your view controller, when receiving the above message, identify the index of the cell for which the button has been tapped (indexPathForCell: might be useful here) and store the fact that the item at index n has been selected. An NSArray might be useful here.
In the same method, force a reload of the cell that has been tapped with reloadItemsAtIndexPaths: or a similar method. This will force the collectionView:cellForRowAtIndexPath: method to be called.
Implement something like the following in your collectionView:cellForRowAtIndexPath: method:
BOOL itemSelected = ((NSNumber *)isProductSelectedArray[indexPath.row]).boolValue; // You can't store `BOOL`s directly into NSArrays. So I've assumed an NSNumber here.
cell.backgroundColor = itemSelected ? [UIColor redColor] : [UIColor clearColor] // Or some other color to indicate non-selection.
As an aside, if you declare “ViewdidLoad” instead of “viewDidLoad”, you might find your code doesn’t behave the way you intend. Don’t forget to call [super viewDidLoad] somewhere in your implementation too.
Finally, I recommend getting a better handle on the concept of cell reuse by reading the “Collection View Basics” chapter of Apple’s “Collection View Programming Guide for iOS” - specifically the section titled “Reusable Views Improve Performance”.
Cells do not maintain a state. An array of objects that correspond to the cells should main the state since cells are recycled very often. For instance, inside you cellForItemAtIndexPath:
....
BOOL isWishListSet = self.isWishListSetArray[indexPath.row];
UIColor *cellColor = [UIColor redColor];
if (isWishListSet) {
cellColor = [UIColor blackColor];
}
cell.backgroundColor = cellColor;
....
EDIT 1:
As gavdotnet mentions in his answer, cell states should be held in a parallel array, not in the cell itself. So you would have one array that holds the data you want to show and another that holds the state of whether the cell has been selected to be on the wishlist:
#interface WishListViewController ()
#property (nonatomic, strong) NSArray *wishListData;
#property (nonatomic, strong) NSMutableArray *wishListStatus;
#end
#implementation WishListViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Initialize arrays
self.wishListData = [NSArray array];
self.wishListStatus = [NSMutableArray array];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.wishListData.count;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
NSNumber *isWishListSet = self.wishListStatus[indexPath.row];
UIColor *cellColor = [UIColor redColor];
if (isWishListSet.boolValue) {
cellColor = [UIColor blackColor];
}
cell.backgroundColor = cellColor;
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
NSNumber *isWishListSet = self.wishListStatus[indexPath.row];
if (isWishListSet.boolValue) {
isWishListSet = [NSNumber numberWithBool:NO];
} else {
isWishListSet = [NSNumber numberWithBool:YES];
}
[self.wishListStatus replaceObjectAtIndex:indexPath.row withObject:isWishListSet];
UIColor *cellColor = [UIColor redColor];
if (isWishListSet.boolValue) {
cellColor = [UIColor blackColor];
}
cell.backgroundColor = cellColor;
}
The section
UIColor *cellColor = [UIColor redColor];
if (isWishListSet.boolValue) {
cellColor = [UIColor blackColor];
}
cell.backgroundColor = cellColor;
is repeated, so it should be in its own method, but that is up to you decide really. The example shows your data array, which populates the cells, and your wishListStatus array which holds the status of the cell. If we were not going to dequeue cells, this would not be an issue. But since we are in this case, the status must be maintained outside of the cell.
The line you are using:
[cell setWishList:[[self wishListData] objectAtIndex:[indexPath row]] delegate:self];
should be changed to something like:
[cell setDelegate:self];
since the delegate is never toggled and is always set to 'self'.
Cells are being reused because of that they are refreshing.
CollectionView reuses cells so the multiple change of background color is the correct behavior.
To fix your problem, customize your UICollectionViewCell(NMCFAIPadWishListCell) instance as follows:
UIView *backgroundView = [[UIView alloc] initWithFrame:self.bounds];
backgroundView.backgroundColor = [UIColor clearColor];
self.backgroundView = backgroundView;
UIView *selectedBGView = [[UIView alloc] initWithFrame:self.bounds];
selectedBGView.backgroundColor = [UIColor redColor];
self.selectedBackgroundView = selectedBGView;
Use the delegate method for extra selection behavior:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
// Instead of button actions, use this delegate method
}
Check out UICollectionViewCell Reference for more details. UICollectionViewCell has three properties backgroundView, selectedBackgroundView and selected which are sufficient for your needs.
I have a UICollectionViewCell subclass called AlbumCVC that contains a single IBOutlet --- a UIImageView called cellView. I'm setting the value of cellView for each cell inside the following method:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell;
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"albumPhotoCell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor blueColor];
if ([cell isKindOfClass:[AlbumCVC class]]){
AlbumCVC *albumCVC = (AlbumCVC *)cell;
ALAsset *thisImage = [self.albumPhotos objectAtIndex:indexPath.item];
albumCVC.imageView.frame = albumCVC.contentView.frame;
albumCVC.contentView.contentMode = UIViewContentModeScaleAspectFit;
albumCVC.imageView.image = [UIImage imageWithCGImage:[thisImage aspectRatioThumbnail]];
}
}
return cell;
}
where albumPhotos is an NSMutableArray of ALAssets. I'm sure that the property is getting set correctly because I get sensible results when I log the albumCVC.cellImage.image.bounds.size. Cells are also sized properly as the frames are visible when I set the background color. But for some reason, cellImage won't display. Is there another method call I need to make inside collectionView:cellForItemAtIndexPath: in order to get the image to show up?
Update: On the advice of a very smart friend, I tried moving the UIImageView out of the cell, putting it elsewhere in the main view, and everything worked lovely. The problem appears to have something to do with the frame / bounds of the UIImageView. I think there's a method call I need to make so that the cell's subview expands to fit the newly-resized cell following the call to collectionView:layout:sizeForItemAtIndexPath:. The problem now is that UIImageView.image.size is a read-only property, so I can't resize it directly.
Update 2: On another piece of advice I looked at the frame and bounds of the cell's contentView and cellImage and found that they weren't matching up. Added another method call to make them equal, and even changed contentMode to UIViewContentModeScaleAspectFit in order to try and get the cell to render the thumbnail properly. Unfortunately, I'm still getting tiny thumbnails inside huge cells. Any idea why? Updated code above and below.
For the sake of completeness, here's the entire class implementation:
#import "AlbumViewController.h"
#import "AlbumCVC.h"
#import <AssetsLibrary/AssetsLibrary.h>
#interface AlbumViewController ()
#end
#implementation AlbumViewController
#pragma constants
const int IPHONE_WIDTH = 320;
#pragma delegate methods
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section{
// Get the count of photos in album at index albumIndex in the PhotoHandler
NSInteger numCells = [self.group numberOfAssets];
return numCells;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell;
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"albumPhotoCell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor blueColor];
if ([cell isKindOfClass:[AlbumCVC class]]){
AlbumCVC *albumCVC = (AlbumCVC *)cell;
ALAsset *thisImage = [self.albumPhotos objectAtIndex:indexPath.item];
}
albumCVC.imageView.frame = albumCVC.contentView.frame;
albumCVC.contentView.contentMode = UIViewContentModeScaleAspectFit;
albumCVC.imageView.image = [UIImage imageWithCGImage:[thisImage aspectRatioThumbnail]];
}
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
//Copy a pointer to an asset from the album
ALAsset *thisImage = [self.albumPhotos objectAtIndex:indexPath.item]; //Zen - are you sure thisImage represents a valid image?
//Copy that asset's size and create a new size struct
CGSize thisSize = thisImage.defaultRepresentation.dimensions;
CGSize returnSize;
// force all previews to be full width
returnSize.width = IPHONE_WIDTH;
returnSize.height = IPHONE_WIDTH * thisSize.height / thisSize.width;
return returnSize;
}
#pragma lifecycle methods
- (void)viewWillAppear:(BOOL)animated{
[self.albumPhotos removeAllObjects];
//"handler" is a class that manages calls to the ALAssetLibrary. self.albumIndex is an integer that gets set on segue. As far as I can tell, everything in the below method is working fine --- cells are sized properly.
self.group = self.albumDelegate.handler.groups[self.albumIndex];
[self.group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
NSLog(#"Just added an object to albumPhotos.");
[self.albumPhotos addObject:result];
NSLog(#"The item in albumPhotos is class %#", [self.albumPhotos[0] class]);
}
}];
}
#pragma instantiation
- (ALAssetsGroup *)group{
if (!_group) {
_group = [[ALAssetsGroup alloc]init];
}
return _group;
}
- (NSMutableArray *)albumPhotos{
if (!_albumPhotos) {
_albumPhotos = [[NSMutableArray alloc]init];
}
return _albumPhotos;
}
#end
Update 3: I can't be certain what the problem was initially, but I know that it now works with the following cellForItem implementation:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell;
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"albumPhotoCell" forIndexPath:indexPath];
if ([cell isKindOfClass:[AlbumCVC class]]) {
AlbumCVC *albumCVC = (AlbumCVC *)cell;
albumCVC.albumImageView.image = [[UIImage alloc] initWithCGImage:[self.albumAssets[[self reverseAlbumIndexForIndex:indexPath.item]] thumbnail]];
}
cell.alpha = [self alphaForSelected:self.selectedItems[#(indexPath.item)]];
return cell;
}
There's no screwing around with frames or bounds anywhere, everything just works. Maybe it's the difference between [[UIImage alloc]initWithCGImage] and [UIImage imageWithCGImage]?
I've had a similar issue and resolved it by setting the UICollectionViewCell frame property to be the same as the UIImageView's frame. I'm not 100% sure that this is your issue, I was building the collection purely in code (no Storyboard)
In the UICollectionView, I've got a custom UICollectionViewCellClass, where prepareForReuse is overridden for default formatting staff.
I've got an NSMutableArray containing NSIndexPaths from didSelectItemAtIndexPath:.
In cellForItemAtIndexPath: I reformat the selected cells so they appear selected.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
ButtonCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"ButtonCell" forIndexPath:indexPath];
NSString *title = self.ingredientsBook.names[indexPath.item];
cell.label.text = title;
if ([self isSelectedIndexPath:indexPath]){
cell.backgroundColor = [UIColor whiteColor];
cell.label.textColor = [UIColor blueColor];
}
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
self.searchButton.enabled = YES;
ButtonCell *cell = (ButtonCell *)[collectionView cellForItemAtIndexPath:indexPath];
[selectedCellIndexPaths addObject:indexPath];
NSLog(#"%#", selectedCellIndexPaths);
cell.backgroundColor = [UIColor whiteColor];
cell.label.textColor = [UIColor blueColor];
NSString *name = self.ingredientsBook.names[indexPath.item];
[self.selectedIngredientNames addObject:name];
}
The problem is that when I tap the first cell it's not possible to select the 16th or 17th.
Or if I tap the first three ones it's not possible to select the three last ones.
The didSelectItemAtIndexPath is not being called I suppose.
I feel that it has to be something really simple but I can't see it right now.
I tried to put NSLogsin shouldSelectItemAtIndexPath for understand if that method was called and the method is not being called at all. This happens when there's a distance of 16 cells between the selected one and the problematic one.
Here are other data source methods and isSelectedIndexPath:
-(BOOL)isSelectedIndexPath:(NSIndexPath *)indexPath{
for (NSIndexPath *test in selectedCellIndexPaths){
if (test == indexPath){
return YES;
}
}
return NO;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return [self.ingredientsBook.names count];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
-(BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"%#", indexPath);
return YES;
}
-(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
self.searchButton.enabled = ([[collectionView indexPathsForSelectedItems] count] > 0);
ButtonCell *cell = (ButtonCell *)[collectionView cellForItemAtIndexPath:indexPath];
cell.backgroundColor = [UIColor blackColor];
cell.label.textColor = [UIColor whiteColor];
[selectedCellIndexPaths removeObject:indexPath];
NSString *name = self.ingredientsBook.names[indexPath.item];
[self.selectedIngredientNames removeObject:name];
}
I found two problems. The prepareForReuse method seemed to be screwing things up, so I just deleted it. The main problem though, was the way you were implementing isSelectedIndexPath:. As soon as it finds the first selected item as you loop through the items, it returns YES and exits the loop. What you want to do, is just check if the indexPath is contained in the selectedCellIndexPaths array:
-(BOOL)isSelectedIndexPath:(NSIndexPath *)indexPath{
if ([selectedCellIndexPaths containsObject:indexPath]) {
return YES;
}else{
return NO;
}
}
Or, if you prefer to use a more succinct syntax, you can replace the if-else block with:
return ([selectedCellIndexPaths containsObject:indexPath])? YES : NO;
I recently faced the exact same issue with my app. UICollectionViewCell selection worked properly prior to iOS 8.3, subsequently I started to see some strange behaviour. Cells that were not actually selected would appear selected, other cells, seemingly at random could not be selected.
I had both custom setSelected and prepareForResuse methods implemented on a UICollectionViewCell subclass as such:
-(void)setSelected:(BOOL)selected
{
[super setSelected:selected];
if (selected)
{
[[self selectedIndicator] setHidden:NO];
}
else
{
[[self selectedIndicator] setHidden:YES];
}
}
-(void)prepareForReuse
{
[[self imageView] setImage:nil];
}
The prepareForReuse method simply reset an image view in the custom cell.
In my prepareForReuse method I did not make a call to [super prepareForReuse] (which according to the documentation does nothing by default). When I added the call to [super prepareForReuse] all selection worked as intended. Although Apple states the default implementation does nothing, they also recommend that super should be called. Following this recommendation solved my issue.
In iOS 10 I found that programmatically clearing a UICollectionView that had multiple-selection enabled was buggy when prefetching was enabled. And of course prefetch is on by default in iOS 10.
I am using a custom cell for my UITableview in which I am having few UILabels. In one label I am setting it's color to red.That is working very fine for me. But As soon as I am reloading the TableView by Egorefresh Table delegate Methods the color of the label of other cells also start changing to red color. I don't know why this problem is coming and it is causing a lot of disgust to me.
This is the code for the TableView datasource method cellForRowAtIndexPath:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath{
// Set the Frame for BackgroundView of Cell.
NSDictionary * insuranceDic = [arr_insurance objectAtIndex:indexPath.row];
insuranceDic = [[AppDelegate sharedInstance] removeNullsFromDictonary:insuranceDic];
InsuranceCell * mycell = (InsuranceCell *)cell;
if([[insuranceDic valueForKey:#"active"] isEqualToString:#"N"]){
//TTTRegexAttributedLabel * label = [[TTTRegexAttributedLabel alloc] init];
mycell.lbl_insuranceName.text = [NSString stringWithFormat:#"%# (%#)",
[insuranceDic valueForKey:#"insurance_name"],#"Inactive"];
mycell.lbl_insuranceName.textColor = [UIColor redColor];
//[label setText:cell.lbl_insuranceName.text withRegex:#"(Inactive)" withFont:
[UIFont boldSystemFontOfSize:12] withColor:[UIColor redColor]];
}
else{
mycell.lbl_insuranceName.text = [insuranceDic valueForKey:#"insurance_name"];
}
}
And on reloading the table view from these methods the problem is occuring :
#pragma mark -
#pragma mark UIScrollViewDelegate Methods
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
// For Top Pull to refresh.
[_refreshHeaderView egoRefreshScrollViewDidScroll:scrollView];
//For Bottom Pull to refresh.
[pullToBottomRefreshManager_ tableViewScrolled];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:
(BOOL)decelerate{
// For Top Pull to refresh.
[_refreshHeaderView egoRefreshScrollViewDidEndDragging:scrollView];
//For Bottom Pull to refresh.
[pullToBottomRefreshManager_ tableViewReleased];
}
#pragma mark -
#pragma mark EGORefreshTableHeaderDelegate Methods
// Belowline is for stop refresh indicator
//[_refreshHeaderView
egoRefreshScrollViewDataSourceDidFinishedLoading:self.tableView];
- (void)egoRefreshTableHeaderDidTriggerRefresh:(EGORefreshTableHeaderView*)view{
// When we going to refresh the data we need to reset the start varible to 1
// and also removeallthe previous objects from the array.
str_start = #"1";
[self getInsuranceList];
}
- (BOOL)egoRefreshTableHeaderDataSourceIsLoading:(EGORefreshTableHeaderView*)view{
return isReloading; // should return if data source model is reloading
}
- (NSDate*)egoRefreshTableHeaderDataSourceLastUpdated:
(EGORefreshTableHeaderView*)view{
return [NSDate date]; // should return date data source was last changed
}
#pragma mark -
#pragma mark MNMBottomPullToRefreshManagerClient Methods
- (void)bottomPullToRefreshTriggered:(MNMBottomPullToRefreshManager *)manager {
// Here we are incrementing start with records per page means start+=recordsperpare
or start+=20.
str_start = [NSString stringWithFormat:#"%d",[str_start intValue]+
[str_recordsPerPage intValue]];
[self getInsuranceList];
}
This is the code for cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
(NSIndexPath
*)indexPath{
static NSString *CellIdentifier = #"Cell";
InsuranceCell * cell = [InsuranceCell dequeOrCreateInTable:tableView];
// UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[InsuranceCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier] autorelease];
}
NSDictionary * insuranceDic = [arr_insurance objectAtIndex:indexPath.row];
insuranceDic = [[AppDelegate sharedInstance] removeNullsFromDictonary:insuranceDic];
cell.lbl_insuranceNo.text = [insuranceDic valueForKey:#"insurance_no"];
cell.lbl_groupNo.text = [insuranceDic valueForKey:#"group_no"];
cell.lbl_priority.text = [self getPriorityDescFromCode:[insuranceDic
valueForKey:#"priority"]];
cell.lbl_startDate.text = [insuranceDic valueForKey:#"start_date"];
cell.lbl_endDate.text = [insuranceDic valueForKey:#"end_date"];
cell.lbl_copay.text = [insuranceDic valueForKey:#"copay"];
// This Condition is For setting alternate (White/SkyBlue) Background color for cell.
// if (fmod(indexPath.row, 2)==0) {
// cell.backgroundView.backgroundColor = [UIColor colorFromRGBIntegers:237 green:243 blue:249 alpha:1];
//
// }
// else{
// cell.backgroundView.backgroundColor = [UIColor colorFromRGBIntegers:250 green:250 blue:250 alpha:1];
// }
cell.selectionStyle = UITableViewCellEditingStyleNone;
return cell;
}
It would help if you could add your cellForRowAtIndexPath: implementation.
Given the information you provide I suppose you are reusing cells and not resetting the color before.
You need to override - (void)prepareForReuse in your InsuranceCell
and reset any cell specific properties.
Sample Implementation:
- (void)prepareForReuse {
[super prepareForReuse];
// reset the label text color to your default color (e.g. black)
self.lbl_insuranceName.textColor = [UIColor blackColor];
}
You need to implement this method within your InsuranceCell.
You need to understand, that with the default implementation of cellForRowAtIndexPath: your TableViewCells will be reused. This means you need to reset every property explicitly, before you reuse a cell. The most convenient way to do this, is overriding the prepareForReuse method, as shown above.