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
Related
I'm getting some weird behavior in my UITableViewController.
I've subclassed UITableViewCell and created by own "visited" property within it.
- (void)setVisited:(BOOL)visited animated:(BOOL)animated
{
[self setVisited:visited];
...
}
I set this property when I create the cell in tableView:cellForRowAtIndexPath: (the only place I create it) like below:
if (cell == nil) {
cell = [[ArticleListViewCell alloc] initWithReuseIdentifier:CellIdentifier art:art index:indexPath.row];
[cell setVisited:NO animated:NO];
}
Later, in tableView:didSelectRowAtIndexPath:, I set this property to YES:
ArticleListViewCell *cell = (ArticleListViewCell *) [tableView cellForRowAtIndexPath:indexPath];
[cell setVisited:YES animated:NO];
However, when I select a cell and then return to this UITableView, which currently has 10 cells, I find that not only has the cell I selected become "visited", but also another cell has as well. It's hard to explain, but if I select the 1st cell, the 7th also becomes visited, if I select the 2nd, the 8th also becomes visited, and so on. Does anyone know why this is, and how I should go about fixing it?
I've checked this question, but it doesn't seem to help much.
This is caused by cell reuse. You need to set visited every time, not just when you create the cell.
if (cell == nil) {
cell = [[ArticleListViewCell alloc] initWithReuseIdentifier:CellIdentifier art:art index:indexPath.row];
}
BOOL visited = ... // value for this indexPath
[cell setVisited:visited animated:NO];
And in your didSelectRow method you need to update some sort of data structure keeping track of which row has been visited. You use this data to set the value properly in the cellForRow method.
Do not use the cell to keep track of state. The cell is a view. Your data source needs to track the state.
I am struggling to figure out how to get complete control over my tableview cells. I want them to look something like this:
Right now I need to know how to properly manage my cells. Should I make a table view cells subclass? Should I be doing this all within the storyboard of the tableview? That's what I'm doing now. Also, how do I implement dynamic cell heights based on the amount of lines of text?
Thanks
You should subclass the UITableViewCell class and create your own custom cell using XIB. This will give you a lot of leg room for dynamism.
Refer to this tutorial for how to do so:
http://www.appcoda.com/customize-table-view-cells-for-uitableview/
U can create a custom view and use the followingin the cellForRowAtIndex
static NSString * cellIdentifier=#"MyTableView";
UITableViewCell * cell;
if(cell== nil)
{
cell = [myTableView dequeueReusableCellWithIdentifier:cellIdentifier];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
contentCell.tag =100;
contentCell=[[ContentOfCell alloc]initWithFrame:CGRectMake(0, 0, 100, 50)];
[cell.contentView addSubview:contentCell];
}
else{
// Reusable part. Reuse the UI controls here from existing cell
contentCell = (ContentOfCell *)[cell.contentView viewWithTag:100];
}
//Assign all the data here
contentCell.nameField.text=[arr objectAtIndex:indexPath.section];
//same way for other fields
}
Where contentCell is a custom view
I will try to answer your question in three parts :
For Dynamic cell height which is based on text content : you have a table view delegate called heightForRowAtIndexPath, you should calculate the height of the text based on its font and font size characteristics, and of course by providing the available width, for this you can use method "sizeWithFont" of NSString.
For more control on the cell appearance : you should make a table view cell subclass and use it in the cellForRowAtIndexPath.
Should you be doing this using storyboard : It is not necessary to do it using storyboard.
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.
Previously I had this set up with a storyboard, having dragged the UILabels, positioned them and sized them whatnot on the UITableViewCell I dragged them onto, and then do a different version of that for the other UITableViewCell.
For example, like follows (but in the picture they've yet to be customized with the labels):
Then in the datasource, I'd simply check the Identifier, and depending on what the Identifier was, customize the cell accordingly.
However, I've needed more customization than I can get from the storyboard, as each cell is going to have two UIViews (a top one and a bottom one to allow sliding of the top one) so I can't really do this with storyboarding, as I add the labels and everything to the UIView programmatically.
But my question is: When I do it programmatically, how can I tell which cell is which so I can customize the layout of the UILabels accordingly? With a storyboard I can obviously just drag a UILabel onto each one, but when doing it programmatically and setting up the UIView, I don't know how to say, "Hey, if the identifier is this, add the UILabels like so" because the UIViews aren't aware of any Identifiers.
Basically the structure looks like this:
UITableView -> UITableViewCell -> CellFront(UIView) & CellBack(UIView)
And the look of the cell comes from the labels added to the CellFront UIView. But there's two looks to the cells and I don't know how to do it without a storyboard.
Although UIViews are not aware of identifiers, they have a property called tag which can be used for any purpose that you would like. You can set the tag to, say, 1 on cells of one kind, and to 2 on cells of the other kind, and then use the tag to distinguish the cells in code. Moreover, once your views are tagged, you can call viewWithTag: on the containing view, and get back the view with the tag that you want.
If you are creating the cells solely in code, then you register your UITableViewCell subclass in the viewDidLoad method of your table view controller. That method sets the identifier. Then, you use that identifier in cellForRowAtIndexPath: just like you would for a xib or storyboard created cell.
[self.tableView registerClass:[MyCellSubclass class] forCellReuseIdentifier:#"MyIdentifier"];
Here is one approach:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Adjust the condition to match your needs
if (indexPath.row == 0) {
static NSString *Identifier1 = #"CellType1";
// cell type 1
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier1];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Identifier1];
// add subviews here
}
// set cell properties
return cell;
} else {
static NSString *Identifier1 = #"CellType2";
// cell type 2
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier2];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Identifier2];
// add subviews here
}
// set cell properties
return cell;
}
}
I am adding a custom button into my cell.contentView, and I noticed that every time a cell is scrolled off the visible part of the screen and back on, the button gets re-added - the translucent parts of it get more and more solid. What is the correct way to handle it so that it does not keep stacking more objects on top when scrolling through the tableView? Note that custom content is different for each cell, so I cannot put it into the if (cell == nil) {...} block.
The code I have is:
UISegmentedControl *btn = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObject:btn_title]];
// set various other properties of btn
...
[cell.contentView addSubview:btn];
Every time the cell is dequeued, you have to remove the old subviews before adding new ones, or else you'll get that stacking effect. You can do this in one of two places:
a) In tableView:cellForRowAtIndexPath:, remove your old views after the dequeueReusableCellWithIdentifier: call and before adding your new ones.
b) If you're using a subclass of UITableViewCell, you can override prepareForReuse to remove unwanted views. prepareForReuse is called every time a cell is dequeued for reuse, so it's a good place to get rid of old views from the last time the cell was configured.
I'll post a sample fix for the code you posted. It can be extended to take care of more views.
The steps are:
Create a method in your CustomCell class that takes care of the whole setup (for example: setupWithItems:)
Once you have a cell in cellForRowAtIndexPath: (after dequeueing it or creating it), you should call setupWithItems: with the new list of items the cell should display.
In your setupWithItems: implementation, make sure you remove the UISegmentedControl from its parent view. You can easily do this it the segmented control is stored as a property of your custom cell.
In your setupWithItems: implementation, create a new UISegmentedControl and add it to the CustomCell's view hierarchy.
Sample code:
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
CustomCell* cell = [tableView dequeueReusableCellWithIdentifier:kSomeIdentifier];
if (!cell)
{
// Create a new cell
}
NSArray* currentCellItems = [self cellItemsForRow:indexPath.row];
[cell setupWithItems:currentCellItems];
return cell;
}
And in your CustomCell subclass:
- (void)setupWithItems:(NSArray*)items
{
if (self.segmentedControl)
{
[self.segmentedControl removeFromSuperView];
self.segmentedControl = nil;
}
// More setup...
UISegmentedControl *btn = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObject:btn_title]];
// set various other properties of btn
[cell.contentView addSubview:btn];
}