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;
}
Related
I have 11 or more number of rows. Need to create a UISwitch only in the first cell. The UIswitch gets duplicated when i click on any row.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:EN_MoreTableViewCell];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:EN_MoreTableViewCell];
cell.backgroundColor = [UIColor clearColor];
}
cell.textLabel.text = languageObject.name;
[cell.textLabel setFont:font];
if (indexPath.row == 0 && [languageObject.name isEqual: #"All Languages"]) {
if (!mySwitch) {
mySwitch = [[UISwitch alloc] initWithFrame:CGRectMake(_languageListTableView.frame.size.width - 60, 0, 40, 40)];
[cell.contentView addSubview:mySwitch];
}
cell.accessoryType = UITableViewCellAccessoryNone;
}
else { //other cell code//
}
Please help.
This is a classic iOS newbie question. This confuses the hell out of most of us when we first use table views (it certainly confused me at first.)
Cells get created once and then reused over and over. The function dequeueReusableCellWithIdentifier() returns a recycled cell if one is available, or creates a new one from your cell prototype if not.
If you get a recycled cell, it will already have your switch added.
The cleanest way to handle this is to define a cell prototype using a custom subclass of UITableViewCell that has all your fields already added and connected as outlets to the cell. Then when you dequeue a cell, simply cast it to your custom UITableViewCell class and use the properties you've defined to access your custom fields (Your switch, in this case.)
A recycled cell may also contain values in it's other fields (Like if you've set a label field to contain a string, it will still contain the string.) You need to clear out old values and completely configure a recycled cell. (The custom cell class and prototype doesn't fix this problem. You always need to fully configure every field in your cell.)
Either:
Create two prototype cells in Storyboard, one with and one without UISwitch and dequeue the first only if indexPath.row == 0.
Or:
Add the UISwitchto your cell in Storyboard, make an IBOutlet to your cell and set self.mySwitch.isHidden = true in cells prepareForReuse().
This way the default state when reusing the cell is with hidden switch.
Later if indexPath.row == 0, set cell.mySwitch.isHidden = false.
It's because cells are reused. You can either remove all existing subviews in -[UITableViewDataSource tableView:cellForRowAtIndexPath:] or create a new cell for every row.
Sometimes a custom cell is a big hammer for just adding a single view to an otherwise perfectly good standard UITableViewCell. For those occasions, lazy creation is a nice pattern to get views built exactly once on reused cells (or even as any subview of any view). It works like this:
// in your cellForRowAtIndexPath, after dequeuing cell
UISwitch *switch = (UISwitch *)[cell viewWithTag:64]; // make up a unique tag
if (!switch) {
switch = [[UISwitch alloc] initWithFrame:...];
switch.tag = 64; // 64 must match the tag above
[cell addSubview:switch];
}
// here, switch is always valid, but only created when it was absent
Add a UISwitch in the storyboard. Connect outlet. In tableview's cellforrowatindexpath, if the index is 0 set hidden=false else set hidden=true. Hope this works.
You just hide the switch where you don't want to display and set the action for that switch dynamically for the particular indexpath you want
how can i build an user profile view like this (Periscope but is similar to many other apps).
It's a tableviewcontroller? If it is, how can i put the image of the user with background (it's in the first cell or above the tableview?)
I'd build it this way:
UIViewController with UITableView + custom UIView on top.
If you want to use already implemented libraries, check this out :
MGSpotyViewController
jcbannerView
Facade
They have pretty similar logic that's described in your question.
It is a custom tableviewcontroller. Everything is a cell but it is hard to create only using storyboard. you create a dynamic table view and ad 3 prototype cell for it (1: Blue cell, 2:Grey empty cell, 3: Option Cell). And create a controller and manage the cell with it like:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexpath.row== 0)
{
HeaderCell *cell = [tableView dequeueReusableCellWithIdentifier:#"headerCell" forIndexPath:indexPath];
cell.name = "foo";
....
}
else if(indexpath.row ==1 || indexpath.row ==3)
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"blankCell" forIndexPath:indexPath];
[cell setBackgroundColor:[UIColor greyColor]];
}else{
....
}
}
Looks like above is a header view, and below a table view.
If the above part scrolls off it is the table view's tableHeaderView. In this case you can use a UITableViewController.
Otherwise you will have to use a UIViewController with a UIView as the header and a UITableView below it, and you have to declare the table view delegate and datasource yourself.
You can achieved same UI with help of bringing UITableView and add UIView with blue background as tableHeaderView like as below.
UIView *headerView = [[UIView alloc] init...];
tableView.tableHeaderView = headerView;
I am trying to create a project with a custom UITableViewCell. The custom cells never load, they're just blank. At this point in the project what I'm trying to do is placing a UITableViewCell in a .xib, designing it the way I want and specifying its reuse identifier along with tag IDs for the components so that I can use them in code later on.
I've googled a ton and found several tutorials that look like what I want to do, along with many SO questions that have answers that seem applicable. At this point it's probably just my head spinning with too many different angles and solutions.
This is my current attempt at trying to register the custom cell with my UITableView, yet when running this on a device the rows in the table view are entirely blank.
UITableViewCell* cell = [self.tableView dequeueReusableCellWithIdentifier:#"MyCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"MyCell"];
}
UILabel* l1 = (UILabel*)[cell viewWithTag:1];
UILabel* l2 = (UILabel*)[cell viewWithTag:2];
UILabel* l3 = (UILabel*)[cell viewWithTag:3];
l1.text = #"Foobar";
l2.text = #"Foobar";
l3.text = #"Foobar";
I'm pretty certain that I've hooked up all the properties and such correctly, but at this stage I need a fresh pair of eyes to point out the facepalm for me.
The interesting files are FilmerView.m/h/xib and the cell is in FilmerViewCell.xib. When running the app this TableView is in the second tab of the tab bar controller.
Project:
http://speedy.sh/WhhpP/test12.zip
I can't provide a full answer atm but look up the tableview method. registerNib:forCellReuseIdentifier:
Also, stop using that dequeue method. Use the one that includes the indexPath.
Then you don't have to check if the cell is nil afterwards.
EDIT
In viewDidLoad (or somewhere like that)...
UINib *cellNib = [UINib nibWithNibName:#"MyCustomCellXibFileName" bundle:[NSBundle mainBundle]];
[self.tableView registerNib:cellNib forCellReuseIdentifier:#"CellIdentifier"];
Now in the table view datasource method...
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier" forIndexPath:indexPath];
// no need to check cell == nil.
// this method is guaranteed to return a non nil cell.
// if it doesn't then the program will crash telling you that you need to...
// register a class or nib (but we just did this in viewDidLoad) :D
// configure your cell here...
[self configureMyCell:(MyCustomCell *)cell atIndexPath:indexPath];
return cell;
}
- (void)configureMyCell:(MyCustomCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
cell.nameLabel.text = #"Hello, world";
}
Hope this helps.
Make sure that you have set datasource and delegate properties of your tableView.
Make sure that you have implemented (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section method and it returns a positive value (>0).
Evaluate the following:
Is the ReuseIdentifier set in the XIB. See Properties Tab in Interface Builder on the right when selecting the cell.
Are the AutoresizingMasks set properly for the labels to be visible?
WrapModes: Which did you select? When having wrapmode WrapWord, is the font size too large for the text to be moved in the next line becoming invisible?
Set the background color of the UITableViewCellss content view to something else than white or transparent, as well as the background colors of the labels to see if the cell is even there.
Manually call numberOfRowsInSection on your table, pass the proper NSIndexPath identifying the target section and see if its greater 0 to confirm that the TableView even attempts to load the data, thus, the cells. ( Alternatively set a breakpoint in the method or do a NSLog. )
Do a NSLog in cellForRowAtIndexPath to confirm that the cell returned is not nil and the method is even called!
I'm having an issue,
I have a simple UICollectionView with a static 200 cells that load images from Flickr.
my CellForItemAtIndexPath looks like this:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
if(![[cell.subviews objectAtIndex:0] isKindOfClass:[PFImageView class]])
{
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
[cell addSubview:imageView];
}
return cell;
}
PFImageView is a subclass of UIImageView that loads a Flickr photo URL on a background thread and then updates it's own image on the main thread - this works fine.
The logic is really simple - I create a cell if there isn't one dequeueable.
If the cell (which I'm expecting to be dequeued and already have a PFImageView) doesn't have a PFImageView, I alloc and init an imageView for the cell and add it as a subview of the cell.
Thus I expect if the cell has been dequeued it should already have a PFImageView as a subview and as we should not get into the if statement to create a new imageView and kick off a new photo download request
Instead what I see is that the cells at the top and bottom of the UICollectionView that 'go off screen' momentarily - when they come back on screen they are not being reused and seemingly a new cell is created and the picture refreshed.
1) How can I achieve a static image once the cell has been created (i.e. not refreshing when the cell goes slightly off screen.
2) Why are the cells not being reused?
Many thanks for your time.
John
UICollectionView will reuse cells for maximum efficiency. It does not guarantee any particular reuse or population strategies. Anecdotally, it seems to place and remove cells based on integer power of two regions — e.g. on a non-retina iPad it might divide your scroll area up into regions of 1024x1024 and then populate and depopulate each of those regions as they transition into and out of the visible area. However you should not predicate any expectations on its exact behaviour.
In addition, your use of collection view cells is incorrect. See the documentation. A cell explicitly has at least two subviews — backgroundView and contentView. So if you add a subview it will be at index 2 at the absolute least and, in reality, the index will be undefined. In any case you should add subviews to contentView, not to the cell itself.
The most normal way of doing what you're doing would be to create a custom UICollectionView subclass that inherently has a PFImageView within it.
I see several potential issues:
You are looking specifically at index 0 of the cell for the child class that you are adding. The UICollectionViewCell may have other views as children, so you can't just assume that the only (or first) child is the one you added.
I don't see that you are calling registerClass:forCellWithReuseIdentifier: or registerNib:forCellWithReuseIdentifier:, one of which is required for proper use of dequeue (https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewCell_class/Reference/Reference.html).
You are only setting the URL of the PFImageView in the case that you have to construct the PFImageView. The idea with dequeuing reusable views is that you will only construct a small subset of the views needed, and the UITableView will recycle them as they move offscreen. You need to reset the value for the indexPath that is being requested, even when you don't construct the new content.
If your case is as simple as you describe, you can probably get away with adding your PFImageView to the contentView property of your dequeued UICollectionView.
In your controller:
// solve problem 2
[self.collectionView registerClass:[UICollectionViewCell class] forReuseIdentifer:#"FlickrCell"];
In collectionView:cellForItemAtIndexPath
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
// solve problem 1 by looking in the contentView for your subview (and looping instead of assuming at 0)
PFImageView *pfImageView = nil;
for (UIView *subview in cell.contentView.subviews)
{
if ([subview isKindOfClass:[PFImageView class]])
{
pfImageView = (PFImageView *)subview;
break;
}
}
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
if (pfImageView == nil)
{
// No PFImageView, create one
// note the use of contentView!
pfImageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.contentView.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell.contentView];
[cell.contentView addSubview:pfImageView];
}
else
{
// Already have recycled view.
// need to reset the url for the pfImageView. (Problem 3)
// not sure what PFImageView looks like so this is an e.g. I'd probably remove the
// URL loading from the ctr above and instead have a function that loads the
// image. Then, you could do this outside of the if, regardless of whether you had
// to alloc the child view or not.
[pfImageView loadImageWithUrl:staticPhotoURL];
// if you really only have 200 static images, you might consider caching all of them
}
return cell;
For less simple cases (e.g. where I want to visually lay out the cell, or where I have multiple children in the content), I typically customize my UICollectionViewCell's using Interface Builder.
Create a subclass of UICollectionViewCell in the project (In your case, call it PFImageCell).
Add an IBOutlet property to that subclass for the view I want to change in initialization (In your case, a UIImageView).
#property (nonatomic, assign) IBOutlet UIImageView *image;
In Interface Builder, create a prototype cell for the UITableView.
In the properties sheet for that prototype cell, identify the UICollectionViewCell subclass as the class.
Give the prototype cell an identifier (the reuse identifier) in the property sheet.
Add the view child in interface builder to the prototype cell (here, a UIImageView).
Use IB to map the IBOutlet property to the added UIImageView
Then, on dequeue in cellForRowAtIndexPath, cast the dequeued result to the subclass (PFImageCell) and set the value of the IBOutlet property instance. Here, you'd load the proper image for your UIImageView.
I am not sure if the cell is being re-used or not. It may be being reused but the subview may not be there. My suggestion would be to create a PFImageViewCollectionViewCell Class (sub class of UICollectionViewCell) and register it as the CollectionView Cell and try. That's how I do and would do if I need a subview inside a cell.
Try adding a tag on this particular UIImageView
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static int photoViewTag = 54353532;
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
PFImageView *photoView = [cell.contentView viewWithTag:photoViewTag];
// Create a view
//
if (!photoView) {
photoView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
imageView.tag = photoViewTag;
[cell.contentView addSubview:imageView];
}
// Update the current view
//
else {
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
photoView.imageURL = staticPhotoURL;
}
return cell;
}
I would really recommend to create your own UICollectionViewCell subclass though.
EDIT: Also, note that I used the contentView property instead of adding it directly to the cell.
I have a custom UICollectionViewCell that has a custom background view which is drawn using one of several colour schemes. The colour scheme for the background view is set in my custom initializer -(id)initWithFrame:andColourPalette: for the View.
I have a similar custom initialiser in my UICustomViewCell subclass but I can't figure out how to call this initialiser when I am setting up the cell in cellForItemAtIndexPath:
Can anyone help me do this? Or offer alternative solution for passing this Dictionary of colours into the Cell to pass on to the subView?
EDIT to show more detail:
This is what I have in my UICollectionView VC:
In ViewWillAppear:
[self.collectionView registerClass:[OPOLawCollectionViewCell class] forCellWithReuseIdentifier:CELL_ID];
self.colourPalette = [OPOColourPalette greenyColourPalette];
In cellForItemAtIndexPath:
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CELL_ID forIndexPath:indexPath];
OPOLawCollectionViewCell *lawCell = (OPOLawCollectionViewCell *)cell;
MainLevel *level = self.collectionData[indexPath.row];
lawCell.delegate = self;
lawCell.colourPalette = self.colourPalette;
In my Custom UICollectionViewCell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// get background view
OPOLawBook *lawBookView = [[OPOLawBook alloc]initWithFrame:CGRectMake(0, 0, 200, 265) andColourPalette:self.colourPalette];
But that doesn't work - I guess because the propertys are not set up.
If I change the last line to this, then it works fine:
OPOLawBook *lawBookView = [[OPOLawBook alloc]initWithFrame:CGRectMake(0, 0, 200, 265) andColourPalette:[OPOColorPalette greenyColorPalette]];
So i guess I need to use a custom intialiser here but I cant figure out how to call it , or from where...
Thanks
Yuo have to register your customCells in collectionView:
[self.collectionView_ registerClass:[YourCustomClass class]
forCellWithReuseIdentifier:#"CustomCell"];
And then in your method cellForItemAtIndexPath:
YourCustomClass *cell = (YourCustomClass *)[collectionView
dequeueReusableCellWithReuseIdentifier:#"CustomCell" forIndexPath:indexPath];
It is done because collectionView might have 1000 cells and 10 visible. You don't keep all of them initialized and reuse when possible.
EDIT
You should set colorPaletter after you deque the reusable cell. Think of it as a container which can hold any color. You need to determine (by indexpath) what color to paint.
You shouldn't do below if your custom cell is in the Storyboard,
[self.collectionView registerClass:[OPOLawCollectionViewCell class] forCellWithReuseIdentifier:CELL_ID];
Because Storyboard take responsibility to register Cell_ID own.
Now, It will conflict to be generated invalid Cell if you use both.
Way off, every answer. The questioner is looking for a way to uniquely identify each cell upon initialization, which happens prior to dequeuing a cell, and prior to a cell's access to its index path property.
The only way to do this is to assign a unique reuse identifier to every cell based on what the index path value will be (assuming you will know what that will be—and, in your case, you will); then, when dequeuing the cell, use the index path to find the cell with the corresponding reuse identifier.
Does this negates the purpose of reuse identifiers? Absolutely not. You'll be reusing that cell every time you need to use it again. Reuse identifiers were not meant to limit you to a cookie-cutter cell for every cell in your collection view; they are also intended to be "unique use" identifiers.