UIImageView in UICollectionViewCell in UITableViewCell bug - objective-c

The settings for the UICollectionView were defined using IB (ie scroll direction: horizontal, etc), and was embedded in UITableViewCell using IB.
UICollectionViewCell displays, images display, however, images are stacked on top of one another, instead of one image per one cell with fidelity.
I made individual UIImageView for each picture as instance variables, and same occurred using if and switch statements in the cellForItemAtIndexPath message.
Since IB was used, it may be a stretch to identify the bug, however, would you please help to identify the bug in case it is obvious from the code? Thanks.
#implementation AccountTableViewCell
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
imageArray = #[[UIImage imageNamed:#"image1.png"], [UIImage imageNamed:#"image2.png"], [UIImage imageNamed:#"image3.png"], [UIImage imageNamed:#"image4.png"], [UIImage imageNamed:#"image5.png"]];
self.oCollectionView.dataSource = self;
[self.oCollectionView setFrame:self.contentView.frame];
[self.contentView addSubview:self.oCollectionView];
self.oCollectionView.backgroundColor = [UIColor clearColor];
[self.oCollectionView reloadData];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return imageArray.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"accountCell" forIndexPath:indexPath];
UIImageView* iv = [[UIImageView alloc] init];
[cell.contentView addSubview:iv];
[iv setFrame:cell.contentView.frame];
iv.image = imageArray[indexPath.row];
return cell;
}
#end

It's because you keep on adding an UIImageView to the cell each time it's dequeued.
Instead, you should subclass the UICollectionViewCell (let's call it "MYCollectionViewCell", add a UIImageView to the cell subclass in the storyboard and set the UIImageView as an outlet on the subclass.
Then, within cellForItemAtIndexPath, set that imageView's image like so:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"accountCell" forIndexPath:indexPath];
cell.imageView.image = imageArray[indexPath.row];
return cell;
}

Related

Get correct button from custom UICollectionViewCell

I have a custom UICollectionView with custom UICollectionViewCell, I have a like button on my cell (that has been connected to the CustomCVCell.h file) and I need to change the background of this button when it gets pressed. What I did was declaring the action for the button on the cellForItemAtIndexPath: method like this:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
// Configure the cell
FolderProducts *item = _feedItems[indexPath.item];
[cell.like addTarget:self action:#selector(likeProduct:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
Then on the button action I tried to change the background like this:
- (void)likeProduct:(UIButton *)button {
[button setImage:[UIImage imageNamed:#"dislike.png"] forState:UIControlStateNormal];
}
It works but then other random cell's like button has their image changed and I can't understand why..
I also tried to retrieve the correct cell by using:
CollectionViewCell *cell = (CollectionViewCell *)button.superview.superview;
And then:
[button setImage:[UIImage imageNamed:#"dislike.png"] forState:UIControlStateNormal];
But the result is still wrong
the problem is due to reusing of the collection view cell because of this u are getting the random button having the same image
the solution for this would be maintaining the array and storing the selected index paths of the liked button
for example u can do like below
Define a custom deleagte in CustomCVCell.h
#import <UIKit/UIKit.h>
#class CustomCVCell; //forword decleration
#protocol CustomCellDelegate <NSObject>
- (void)customCell:(CustomCVCell *)cell actionForButton:(UIButton *)inButton; //hear u are passing the cell and the button
#end
#interface CustomCVCell : UICollectionViewCell
#property (weak, nonatomic) IBOutlet UIButton *like ; //a button with outlet
#property (weak, nonatomic) id<CustomCellDelegate> cellDelegate;// define a custom delegate
- (IBAction)likeProduct:(UIButton *)sender; // set action to cell not in the controller
//... other code
#end
and in CustomCVCell.m
#import "CustomCVCell.h"
#implementation CustomCVCell
- (id)initWithFrame:(CGRect)frame
{
self = [CustomCVCell cell];
if (self)
{
}
return self;
}
- (void)awakeFromNib {
// Initialization code
}
//handle button action in the cell
- (IBAction)likeProduct:(UIButton *)sender
{
//this action you want it to be in controller, call a delegate method
if([self.cellDelegate respondsToSelector:#selector(customCell:actionForButton:)])
{
[self.cellDelegate customCell:self actionForButton:sender]; //implent the action in the controller
}
}
#end
and in controller
- (void)viewDidLoad
{
[super viewDidLoad];
UICollectionViewFlowLayout *aFlowLayout = [[UICollectionViewFlowLayout alloc]init];
//..set up code
[_aCollectionView registerClass:[CustomCVCell class] forCellWithReuseIdentifier:#"CELL"];
likedCells = [[NSMutableArray alloc]init]; //to hold the index paths of the liked cells
}
//..other code
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomCVCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CELL" forIndexPath:indexPath];
if([likedCells containsObject:indexPath]) //based on the indexaths paths u can set the images of the liked cell and if not set nil
{
[cell.like setBackgroundImage:[UIImage imageNamed:#"22.jpg"] forState:UIControlStateNormal];
}
else
{
[cell.like setBackgroundImage:nil forState:UIControlStateNormal];
}
cell.backgroundColor = [UIColor greenColor]; //for testing
cell.cellDelegate = self; //this must be important
return cell;
}
//as the like button cliks this method will trigger since u are passing the cell and the button, u can get the index path of the cell
- (void)customCell:(CustomCVCell *)cell actionForButton:(UIButton *)inButton
{
//hear u will get both cell and its button
//[inButton setImage:[UIImage imageNamed:#"22.jpg"] forState:UIControlStateNormal];
[inButton setBackgroundImage:[UIImage imageNamed:#"22.jpg"] forState:UIControlStateNormal]; //set the image
[likedCells addObject: [_aCollectionView indexPathForCell:cell]]; //and store the index path in the likedCells array
}
that's it if u want reverse the action same remove the index path
The basic idea is that you need to convert the buttons coordinate space to the collection views coordinate space and then retrieve the indexPath.
If you need to use Objective-C, lets create a function that returns the indexPath of a given cell's subview:
- (NSIndexPath *)indexPathForCellContainingView:(UIView *)view inCollectionView:(UICollectionView *)collectionView {
CGPoint viewCenterRelativeToCollectionView = [collectionView convertPoint:CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds)) fromView:view];
NSIndexPath *cellIndexPath = [collectionView indexPathForItemAtPoint:viewCenterRelativeToCollectionView];
return cellIndexPath
}
Now in your button handler:
- (void)likeProduct:(UIButton *)button {
[button setImage:[UIImage imageNamed:#"dislike.png"] forState:UIControlStateNormal];
NSIndexPath *buttonIndexPath = [self indexPathForCellContainingView:button];
UICollectionViewCell *tappedCell = [collectionView cellForItemAtIndexPath:buttonIndexPath];
}
Remember, your cell will be reused when it scrolls out of bounds so you definitely need to keep track of this state. Either you persist it to disk or you have a simple NSDictionary that manages the state of each button.
I have a convenient Gist in Swift that solves exactly that for UICollectionView and UITableView as well.
There are similar questions here and here
Assign a tag to your button to identify it.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
// Configure the cell
FolderProducts *item = _feedItems[indexPath.item];
[cell.like addTarget:self action:#selector(likeProduct:) forControlEvents:UIControlEventTouchUpInside];
cell.like.tag = indexPath.item;
return cell;
}
Then in the implementation method add below code,
- (void)likeProduct:(UIButton *)button {
UIButton *btn = (UIButton *)sender;
[button setImage:[UIImage imageNamed:#"dislike.png"] forState:UIControlStateNormal];
}
Note : The best way to achieve multiple/single selection is via id associated with your likes. Store that id using collection view then after reload the table from the button method.
Let me know if you still need more help..
You can set tag of button & retrieve tag in your selector.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
// Configure the cell
FolderProducts *item = _feedItems[indexPath.item];
cell.like.tag = indexPath.item
[cell.like addTarget:self action:#selector(likeProduct:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
because your not setting tag for your buttons in cell. So these all are selected at once. You can set their tag like indexPath.item and catch their tag in didSelectItemAtIndexPath and select the desired item.

UICollectionViewCell: Add a view in cell on tap

I have a uicollectionview and I am trying to add a a view to the collectionview cell when it is tapped.
Here is the code I have tried to implement
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
WeekCell *cell = (WeekCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSLog(#"%#", cell.descLabel.text);
UIView *view = [[UIView alloc]initWithFrame:cell.backgroundView.bounds];
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(10, 10, 40, 40)];
label.text = #"new label";
[view addSubview:labels];
view.backgroundColor = [UIColor whiteColor];
[cell.backgroundView addSubview:view];
}
Here WeekCell is a custom UICollectionViewCell with a property view , backgroundview. You will also notice an NSLog in the code. This verifies that the correct cell is being retrieved. What is not working is according to the code, the text should change to white with a new UILabel but this is not the case. The cell's appearance does not change.
EDIT
As suggested I have tried to directly modify the model and call the reloadItemsAtIndexPaths to reload the data. I get an issue where the the "tapped behaviour" is being copied on to untapped cells.
Here is the new code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
if([[modelArray objectAtIndex:indexPath.row] isEqualToString:#"1"]){
cell.overviewView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:imageString]];
}else{
cell.overviewView.alpha = 0;
}
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
WeekCell *cell = (WeekCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSLog(#"%#", cell.descLabel.text);
if([[modelArray objectAtIndex:indexPath.row] isEqualToString:#"1"]){
[modelArray setObject:#"0" atIndexedSubscript:indexPath.row];
}else{
[modelArray setObject:#"1" atIndexedSubscript:indexPath.row];
}
NSLog(#"sequence : %#", modelArray);
[collectionView reloadItemsAtIndexPaths:#[indexPath]];
}
What I am doing is changing the alpha value of the view in the tapped cell to 0. This causes the other cells at random order to disappear once I scroll.
Instead of:
[cell.backgroundView addSubview:view];
... add to content view:
[cell.contentView addSubview:view];
Remarks:
Direct manipulation on collection view cell can cause unexpected results. For instance if cell gets reused. Better to update the model that gets rendered by the cell and call reloadItemsAtIndexPaths: that will automatically(internally) call collectionView:cellForItemAtIndexPath: which should configure(or invoke configuration routine) for the cell that can adjust its presentation.
UPDATE:
You should reset alpha of cells that should be visible, bellow is collectionView:cellForItemAtIndexPath: method with correction:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
if([[modelArray objectAtIndex:indexPath.row] isEqualToString:#"1"]){
cell.overviewView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:imageString]];
cell.overviewView.alpha = 1;
} else {
cell.overviewView.alpha = 0;
}
}
The backgroundView property of a UICollectionViewCell is placed behind the content view. So the reason for the label not being visible could be because it is masked by the content view.
You could set clearColor for the Content view views or add the new view to the contentView;
[cell.contentView addSubview:view];
Hope this information is helpful.

How to link a UICollectionViewCell to a UICollectionView

I have a UIViewController with a UICollectionView on it but Xcode doesn't seem to look like every tutorial I find on the Internet or on YouTube - When I drag a UICollectionViewCell to place in the UICollectionView, it won't let me place it.
Now I'm confused as to how I can link my cell to the UICollectionView.
This is the viewController.h file:
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return [self.imagesArray count];
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
ImageViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"imageCell" forIndexPath:indexPath];
NSString *myImageString = [self.imagesArray objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:myImageString];
return cell;
}
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
return CGSizeMake(100.0, 100.0);
}
-(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
return UIEdgeInsetsMake(5, 5, 5, 5);
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
//[self.collectionView registerClass:[ImageViewCell class] forCellWithReuseIdentifier:#"imageCell"];
self.imagesArray = #[#"shirt1.PNG", #"pants.png", #"pants2.png"];
}
I'm not using a Storyboard interface but individual xib's. When I run this all that appears is the blank black screen. What am I missing?
-registerNib:forCellWithReuseIdentifier:
If you have your cell defined in a NIB, then you register that NIB with the collection view. That is how the collection view know what to load when -dequeueReusableCellWithReuseIdentifier:forIndexPath: is called.
- (void)viewDidLoad
{
// …
UINib nib = [UINib nibWithNibName:#"<the name of your xib>" bundle:nil];
[self.collectionView registerNib:nib forCellWithReuseIdentifier:#"imageCell"];
// …
}
Because you didn't have new a object of UICollectionViewCell in this method:
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
The "cell" in your method must be set to nil.

UICollectionViewCell NSindexpath reference position

How do I reference a specific UICollectionViewCell so that each of them segue to different VCs?
Here is the code for the cells:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
Cell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MY_CELL" forIndexPath:indexPath];
cell.imageView.image = [UIImage imageNamed:self.myCellImages[0]];
UIImage *cellImage = [[UIImage alloc] init];
cellImage = [UIImage imageNamed:[self.myCellImages objectAtIndex:indexPath.row]];
cell.imageView.image = cellImage;
return cell;
}
So this displays a bunch of different cells depending on how many images I load in to my array.
say my array is:
self.myCellImages = #[#"1.jpg", #"2.jpg",.... #"20.jpg"];
how do I reference an individual cell so that it segues into different VC? For example, clicking on the cell that has image 1.jpg segues into 1VC and clicking on the cell with17.jpg segues to 17VC
Is it something like:
if (NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];)
{
//segue here
}
I can't figure out the correct syntax
Thanks to Timothy, I added
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:indexPath
{
NSString *storyboardId = #"main_to_VC1";
[self performSegueWithIdentifier:storyboardId sender:indexPath];
}
You need to establish segues between your collection view controller (not the cell) and the destination controllers, giving each a unique storyboard ID.
Then in the collectionView:didSelectItemAtIndexPath: delegate method, decide which controller you want to segue to based on the indexPath and initiate the segue programmatically:
- (void)collectionView:collectionView didSelectItemAtIndexPath:indexPath
{
NSString *storyboardId = ...; // determine storyboard ID based on indexPath
[self performSegueWithIdentifier:storyboardId sender:indexPath];
}

setScrollDirection: for CollectionView is not working

I implemented a Collection View programatically in a ViewController and I connected it with the Storyboard but the scrolling is not working and half of the cells do not appear since they are faded to the right:
- (void)viewDidLoad {
[super viewDidLoad];
[self.collectionView registerClass:[FotoCell class]
forCellWithReuseIdentifier:#"cell"];
UICollectionViewFlowLayout *myLayout = [[[UICollectionViewFlowLayout alloc]init]autorelease];
[myLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[self.collectionView setCollectionViewLayout:myLayout];
}
Do you know why?
You need to remove the registerClass line in viewDidLoad and set the reuse identifier in the Datasource method for UICollectionViewDelegate as follows -
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath: (NSIndexPath *)indexPath
{
FotoCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
....
return cell;
}