I've encountered a problem while dealing with nested collection views.
Hierarchy is as follows:
outerCollectionView
UIImageView
Label
innerCollectionView
Label
UIImageView
OuterCollectionView's cells are of the size of the screen (works like swiping through pages)
I've set outerCollectionView's delegate and datasource to be UIViewController and innerCollectionView's delegate and datasource to be the containing cell as follows:
in the ViewController.m
-(FeaturedPageCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
FeaturedPageCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"FeaturedCell" forIndexPath:indexPath];
cell.nestedCollectionView.dataSource = cell;
cell.nestedCollectionView.delegate = cell;
[cell.nestedCollectionView setBackgroundColor:[[UIColor whiteColor] colorWithAlphaComponent:.01]];
cell.venueThumbnail.image = [UIImage imageNamed:[self.venueThumbnails objectAtIndex:indexPath.item]];
cell.venueNameLabel.text = [self.venueNames objectAtIndex:indexPath.item];
cell.venueNameLabel.textColor = [UIColor whiteColor];
cell.venueAddressLabel.text = [self.venueAddresses objectAtIndex:indexPath.item];
cell.venueAddressLabel.textColor = [UIColor whiteColor];
[cell setBackgroundColor:[[UIColor grayColor]colorWithAlphaComponent:.1]];
return cell;
}
in implementation file for the cell that contains innerCollectionView
-(FeaturedEventCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
FeaturedEventCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"FeaturedEventCellReuseIdentifier" forIndexPath:indexPath];
cell.eventDateLabel.text = [self.events objectAtIndex:indexPath.item];
cell.eventDateLabel.textColor = [UIColor whiteColor];
cell.eventImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"%#Flyer%ld.png", self.venueNameLabel.text, (long)indexPath.item +1]];
return cell;
}
In this block of code I am using value of containing cell's label to load relevant to that cell data into innerCollectionView.
Pretty straight forward implementation. The result is not what I expected at all. Basically what's happening is that data gets loaded properly into cells of outerCollectionView, but not into the cells of innerCollectionView ( the very first one get loaded correctly, but not the rest of them). Also scrolling through items in innerCollectionView will cause scrolling in the rest of the cells.
I thought that making the containing cell a delegate of innerCollectionView would manage it's own collection view independently from others. I think am not understanding something fundamental about collection views, dequeuing and reusing.
Also is nesting collection views a good way of implementing of my scenario? I tried to look up on implementing nested collection views and couldn't find a whole lot maybe for a reason?
Please, explain what I am doing wrong in my case or direct me towards correct or better solution.
Related
I am trying to display an array of images in a UICollectionView. The cells are being displayed, but the images are not.
Here is the cell being built:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"Cell";
UICollectionViewCell *cell =
[collectionView dequeueReusableCellWithReuseIdentifier:identifier
forIndexPath:indexPath];
UIImageView * catagoryImage = [[UIImageView alloc] initWithFrame:CGRectMake((cell.frame.origin.x),(cell.frame.origin.y), cell.frame.size.width, cell.frame.size.height)];
[catagoryImage setImage:[UIImage imageNamed:[Apps objectAtIndex:indexPath.row]]];
[cell.layer setCornerRadius:4];
[cell addSubview:catagoryImage];
cell.clipsToBounds = NO;
return cell;
}
And here is the array being declared:
Apps = [NSArray arrayWithObjects:#"Appstore.jpg", #"2.gif", #"3.gif", #"4.gif", #"5.gif", #"7.gif", #"8.gif",#"9.gif", nil];
Any ideas?
You have a few options, but I'll name 2:
This. It's a bit hacky. but it works
Subclass UICollectionViewCell
There are two problems with the code. The first, bigger one, is the idea of creating an image view on every invocation of cellForItemAtIndexPath.
This method is called every time a cell is scrolled into view, and the cells are reused, so your cells will end up with piles and piles of UIImageViews on top of one another as the user scrolls around.
Problem two is the coordinate system of the imageView frame. It should be placed relative to the cell's bounds, not the cell's frame which is in the parent view's (collection view's) coordinate space, so...
// see if this cell has an image view already
UIImageView *categoryImageView = (UIImageView *)[cell viewWithTag:128];
if (!categoryImageView) {
// only create one if we don't have one
CGRect frame = cell.bounds; // this is zero based, relative to the cell
categoryImageView = [[UIImageView alloc] initWithFrame:frame];
categoryImageView.tag = 128; // so we can find it later
[cell addSubview:categoryImageView];
}
// add it conditionally, but configure it unconditionally
categoryImageView.image = [UIImage imageNamed:[Apps objectAtIndex:indexPath.row]]];
Another possible problem with the code is access to the Apps objectAtIndex:. Apparently Apps is an unconventionally named instance of an NSArray. This is only okay if your numberOfItemsInSection datasource method returns Apps.count, and if all elements in the array are names of images in your app bundle.
Sussed it.
The images were outside the keyboard scheme and were in the container package.
I want to show last visible cell with different background color (say green).
I am using this code, It works good until I scroll up/down. When I scroll up/down it gives many cell with green background. (I guess this happening due to "dequeueReusableCellWithIdentifier").
Can anyone help me out where I am doing wrong or what is the best way of doing this.
NSArray* indexPaths = [tableView indexPathsForVisibleRows];
if (indexPath.row==[indexPaths count]-1)
{
UIView *v=[[UIView alloc]initWithFrame:cell.bounds];
v.backgroundColor=[UIColor greenColor];
cell.backgroundView=v;
}
Thanks in advance.
Firstly, you shouldn't be allocating anything in cellForRowAtIndexPath - it is bad for performance. You should only be configuring views and controls that already exist for every cell. Because cells are reused, your cell that you add a background view to will get re-used too... and its background view.
In your case, you probably just want to set:
if (lastCell)
cell.contentView.backgroundColor = [UIColor greenColor];
else
cell.contentView.backgroundColor = [UIColor clearColor]; //or whatever color
contentView is already a view every UITableViewCell has for free, so just use that for background color. Apple probably intended it to be used for this case, amongst others.
You should nil the background view before the test.
cell.backgroundView = nil;
if (indexPath.row==[indexPaths count]-1)
{
UIView *v=[[UIView alloc]initWithFrame:cell.bounds];
v.backgroundColor=[UIColor greenColor];
cell.backgroundView=v;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row==[yourtableviewarray count]-1)
{
UIView *v=[[UIView alloc]initWithFrame:cell.bounds];
v.backgroundColor=[UIColor greenColor];
cell.backgroundView=v;
}
}
I am using a UICollectionview to show a lot of Custom cells (250 more or less).
Those cells have a main Image and some text. As the images have to be downloaded from the Internet I am using the external library AsyncImageView to do the lazy load stuff.
But the problem is that the reusable property of the cells are making me crazy.
When I scroll the images appear in the wrong cells. How can I add a tag or something to the images apart from the indexpath to avoid the problem?
Maybe AsyncImageView has a solution to the problem which I ignore ...
Or another alternative would be a better choice?
Any clue?
Thanks in advance
Edit: A simplified version of my code
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"Cell";
CollectionComercioCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
if (cell == nil){
}
else{
[[AsyncImageLoader sharedLoader] cancelLoadingImagesForTarget: cell.myImage];
}
cell.myImage.imageURL = nil;
cell.myImage.image = nil;
cell.myImage.hidden = TRUE;
cell.myImage.imageURL = [[myArray objectAtIndex:indexPath.row] getLogoUrl];
cell.myText.text = [[myArray objectAtIndex:indexPath.row] getName];
cell.myImage.hidden = FALSE;
return cell;
}
CustomCell.m
- (void)prepareForReuse
{
[super prepareForReuse];
self.myImage.image = nil;
}
Make sure you set the image to nil in your cellForRowAtIndexPath: method. This way it will stay at least empty until the new image is loaded.
As you mentioned in your comments to the question that this is works if you scroll slow and not when you scroll fast you could maybe override - (void)prepareForReuse on your custom cell.
But be aware that Apple warns you not to use it for content changes:
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 made a UICollectionView with some cells in it and it displayed correct, now I want to set a selected tag for one or more cells, in custom cell, I can use two ways to implement it:
way 1: set selectedBackgoundView
self.selectedBackgroundView = backgroundView;
way 2: add a UIImageView as selected tag
[_coverImageView addSubview:_selectImageView];
//coverImageView is image for cell,
//selectImageView is a tag imageView for selected.
then the problem comes up:
For example I selected the first cell, When I scroll the UICollectionView, way 1 still displayed the first cell selected, but with way 2, the _selectImageView would be added to the other cell.
I know it is caused by Reuse Cell,but have no idea for deal with it.
Rather than adding your selected tag after you've created the cell, you should add it at the point of creation.
You don't say how you're creating your custom collection view cells, but it sounds as if you might not be using your own subclass, and are simply adding what you need to a plain UICollectionViewCell. You will find it much easier to create your own subclass, and set it up with an image view exposed that can be enabled/disabled as required. You can create custom cells either entirely in code, or in conjunction with a XIB - whichever you prefer.
Recently I am working on a similar project. Although It's long ago, but I hope to help someone who need it.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
MyCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:collectionCellID forIndexPath:indexPath];
if (cell == nil) {
cell = [[MyCollectionViewCell alloc]init];
}
//Change Selected State
if([[collectionView indexPathsForSelectedItems] indexOfObject:indexPath] != NSNotFound){
UIView *bgView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 250, 250)];
bgView.backgroundColor = kLightBlueColor;
[cell setSelectedBackgroundView:bgView];
cell.selected = YES;
}
cell.title.text = #"Hello World";
return cell;
}
I have a UITableViewCell, it is scroll-disabled and with fixed sections and rows.
There are two sections, with 1 row in section 0, and several rows in section 1.
The tableView is for users to make some choices.
So, the first section (with only one row) is going to display the result of users' choices,
and no doubt the second section (with several rows) is for choosing.
Now I want to put an image in the cell of the only row of the first section,
and this image will change according to users' choose.
It is very easy to judge which png image should be displaying, but I have trouble update it.
I tried use the cell's imageView, and manually alloc a UIImageView or UIView there to display those images.
But all of them won't work, I mean they just keep what they are displaying at the beginning and never changes it, even if I set the view's background or its image to a new png.
I tried some method like
[myImage setNeedsDisplay] for the manually alloced view,
or
[thatCell setNeedsDiaplsy] & [self.tableView reloadData] for the imageView of that cell,
but in vain.
So I wonder how can I achieve this function that dynamically display an image in a UITableViewCell in different situations?
Thanks a lot!
_____update line_____
I'm sorry that I didn't provide my code.
and here they are.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *inOutTableCellIdentifier = #"DealTableViewIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:inOutTableCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
reuseIdentifier:inOutTableCellIdentifier] autorelease];
cell.textLabel.font = [UIFont fontWithName:cMyFont size:[UIFont systemFontSize]];
cell.detailTextLabel.font = [UIFont fontWithName:cMyFont size:[UIFont smallSystemFontSize]];
// I tried using both imageView and UIView here, but won't work
if (indexPath.section == 0) {
//cell.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"moneyCell.png"]];
cell.backgroundColor = [UIColor cyanColor];
// cell.imageView.image = [UIImage imageNamed:#"dealDone2.png"];
self.undoneIcon = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)] autorelease];
//self.undoneIcon.image = [UIImage imageNamed:#"dealUndone2.png"];
self.undoneIcon.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"dealUndone2.png"]];
[cell.contentView addSubview:self.undoneIcon];
.... // deal with other rows in section 1
return cell;
}
// and this is the method that update the image, the images are named "dealDoneX.png",
// where X is an integer from 0 to 4.
- (void)checkUndoneDegree { // here I also tried 2 ways corresponding to UIView or a cell's imageView, but neither works well.
int i = 0;
if (self._date)
i++;
if (self.moneyTextField.text)
i++;
if (self._incomingAccount)
i++;
if (self._expensingAccount)
i++;
if (_dealType != kTransferView)
if (self._type)
i++;
NSLog(#"undone degree: %d",i);
NSString *imageName = [#"dealUndone" stringByAppendingFormat:#"%d",i];
self.undoneIcon.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:imageName]];
[self.undoneIcon setNeedsDisplay];
// NSUInteger p[2] = {0,0};
// UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathWithIndexes:p length:2]];
// [cell setNeedsDisplay];
}
and everytime I update the table's data, like changing some text of some cell,
I would call [self checkUndoneDegree] and then call [self.tableView reloadData],
But the picture is never updated, at least from the screen.
I even tried to put the codes that decide which png to set in the
(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
method, but it can only make the view displaying the first png, without any updating.
Thanks for helping!
Make your undoneDegree (represented by i variable in your code) an ivar of your view controller, so it is accessible in all of it's methods, also in the UITableView delegate and data source methods.
Forget about setNeedsDisplay method. Since you are using UITableView to display your content, you need to play by its rules. This means you should use reloadSections:withRowAnimation: method.
Check again if you need self.undoneIcon. I'm pretty sure that imageView property should do. That is if you really want to display an image in the cell. If you want to create some kind of progress bar by manipulating cell's background, then use backgroundView property, or just place UIProgressView in your cell. This is what I think your want to do, after looking at your code.