I have a custom UITableViewCell that has a method called updateCell. It adds a spotView which is just a UIView and self.spotView has a couple of subviews.
The issue is that when I scroll, the spotView will start off fine but when you scroll, it fills up with these subviews and inaccurate information. How do I fix this?
I have tried in prepareForReuse in my custom UITableViewCell and have tried to removeFromSuperview the subviews of spotview but that doesn't work either.
I have but am a bit confused what I need to get this working correctly. I see that I can set uilabel's to nil in prepare for reuse and that seems to work but these UIViews seem to hang out:
-(void)updateCell:(MenuItem *)item
{
self.spotView =[[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 10.0f, 10.0f)];
[self.contentView addSubview:self.spotView];
[self renderHeader:menuItem.header];
[self renderDetail:menuItem.detail];
[self renderMeta:menuItem];
...
}
- (void)renderMeta:(MenuItem *)menuItem{
if([self.spotView.subviews count]>0){
NSLog(#"THERE ARE subviews in spotView in renderMeta for %#", menuItem.header);
}else{
NSLog(#"NO subviews in spotView in renderMeta for %#", menuItem.header);
}
for (UIView* view in [self.spotView subviews])
{
NSLog(#"!!!about to remove subviews here!!!");
[view removeFromSuperview]; // <- not working
}
if([menuItem hasInstoreImage] || [menuItem hasTastingNotes]){
if([menuItem hasInstoreImage]){
UIView *instoreImageDot=[self circleWithColor:[UIColor redColor] radius:4];
NSLog(#"adding instoreImageDot in renderMeta");
[self.spotView addSubview:instoreImageDot];
}else{
NSLog(#"no instoreImageDot in renderMeta");
}
edit #1
Playing around with this, I found that trying to manipulate the views in the custom UITableViewCell was a big unpredictable. I was doing all of the custom UITableViewCell in code since I would be distributing as a Cocoapod internally to the company. What DID work was manipulating the cell in the ViewController specifically like:
// Does not work on iOS below 6.0
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if([cell isKindOfClass:[MenuItemCell class]]){
NSLog(#"A MenuItem Class");
MenuItemCell *miCell=(MenuItemCell *)cell;
[miCell.spotView removeFromSuperview];
}
}
edit #2
Where / How updateCell is called (somewhat abbreviated as some code is taken out):
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
[self.menuTV registerClass:[MenuItemCell class]forCellReuseIdentifier:#"MenuItemCell"];
static NSString *MenuItemCellIdentifier=#"MenuItemCell";
id dic=self.menu.listItems[indexPath.row];
if([dic isKindOfClass:[EMBERSMenuItem class]]){
//MenuItemCell *cell = [self.menuTV dequeueReusableCellWithIdentifier:MenuItemCellIdentifier];
MenuItemCell *cell = [self.menuTV dequeueReusableCellWithIdentifier:MenuItemCellIdentifier forIndexPath:indexPath];
//MenuItem *menuItem=(MenuItem *)dic;
MenuItem *menuItem=(MenuItem *)dic;
cell.menuItem=menuItem; // <- probably shouldn't have this
[cell updateCell:menuItem];
}else{
You should add your subviews in your cell in the cell's init methods. Otherwise, if you add subviews every time the cell gets rendered your cell will end having as many subviews as the rendering happens.
As an example:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self.contentView addSubview:mySubview];
}
}
And then you can have a configuration method to inject your cell's data:
- (void)configureCellWithData:(id)data {
self.mySubview.setText(data.text);
}
Related
Hi i am new for ios and in my app i have created one UITableView and i have set background image for UITableViewcell but image not filling the whole width of screen as like below screen. Why this problem is occuring?
I mean UITableViewCell left and right sides gap is coming images is not filling whole cell width.
please help me someone
my code:-
#import "TableViewController.h"
#interface TableViewController ()
{
UITableView * tableList;
TableCell * Cell;
}
#end
#implementation TableViewController
- (void)viewDidLoad {
[super viewDidLoad];
tableList = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, [[UIScreen mainScreen]bounds].size.width, [[UIScreen mainScreen]bounds].size.height) style:UITableViewStylePlain];
tableList.delegate = self;
tableList.dataSource = self;
tableList.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.view addSubview:tableList];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = #"MyCell";
Cell = (TableCell *)[tableList dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (Cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"TableCell" owner:self options:nil];
Cell = [nib objectAtIndex:0];
}
//UIImageView *imageBackground = [[UIImageView alloc] init];
if (indexPath.row == 0) {
Cell.backGroundImage.image = [UIImage imageNamed:#"cell_top.png"];
} else if (indexPath.row == 9) {
Cell.backGroundImage.image = [UIImage imageNamed:#"cell_bottom.png"];
} else {
Cell.backGroundImage.image = [UIImage imageNamed:#"cell_middle.png"];
}
//imageBackground.contentMode = UIViewContentModeScaleToFill;
//Cell.backgroundView = imageBackground;
return Cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 44.0;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if ([cell respondsToSelector:#selector(setSeparatorInset:)]) {
[cell setSeparatorInset:UIEdgeInsetsZero];
}
if ([cell respondsToSelector:#selector(setPreservesSuperviewLayoutMargins:)]) {
[cell setPreservesSuperviewLayoutMargins:NO];
}
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
#end
Try to set the layoutMargins property of the cells and the UITableView to UIEdgeInsetsZero.
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
tableList.layoutMargins = UIEdgeInsetsZero;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
[...]
Cell.layoutMargins = UIEdgeInsetsZero;
return Cell;
}
Also check for the contentMode of the UIImageview.
Cell.backGroundImage.contentMode = UIViewContentModeScaleAspectFill;
try set contentInset on Left = 0
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
Use Debug View Hierarchy to figure out UITableView, UITableViewCell or UIImage is not filling the whole width of screen
http://www.raywenderlich.com/98356/view-debugging-in-xcode-6
Please check your "TableCell" in the storyboard. Did you select custom insets option for your custom cell?
Rather than setting up your table view with code, you want to do this in a storyboard. Then you'll want to use auto layout to connect constraints from the table view to the view controller's view. There are lots of tutorials available to teach you how to do this. Learning this will make things much easier in the long run.
Change the name of your tableList property to tableView. That will make more sense to other developers (including yourself in the future), since that's what it is (a UITableView instance).
Your cell is named Cell with a capital C, but you don't want to name properties with capital letters. Also, it doesn't need to be a class property the way it's being used. Remove it from the #interface section.
Coding Guidelines for Cocoa
Remove the -numberOfSectionsInTableView: method. The default is 1, so you don't need code to return the default value.
Instead of -dequeueReusableCellWithIdentifier:, use -dequeueReusableCellWithIdentifier:forIndexPath:. Then you won't need to follow it with a test to see if a cell was returned (it always will be). You'll need to register your nib with -registerNib:forCellReuseIdentifier:. Or better yet, just design it in the storyboard.
It appears that your custom table view cell has a UIImageView named backGroundImage. That should be added as a subview to the cell's backgroundView property (which you'll need to create - the view, not the property, which is already part of UITableViewCell). Set the image view's autoresizingMask so it will resize with the backgroundView:
- (void)awakeFromNib
{
[super awakeFromNib];
self.backgroundView = [[UIView alloc] initWithFrame:self.bounds];
self.backGroundImage.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.backGroundImage.frame = self.backgroundView.bounds;
[self.backgroundView addSubview:self.backGroundImage];
}
Remove the -tableView:heightForRowAtIndexPath: method. You only want to use this if you return different values. The default row height is 44.0, so you don't need to do anything else.
I have a custom UITableViewCell, and when it's selected, it expands and adds a UILabel to the selected cells UIView that I added in the storyBoard.
When I run the app and select a cell, the label gets added to myView as expected. The problem is, when I scroll down, the label is also shown at another cell.
Apparently the reason its behaving like so, is because I'm reusing the cell and I don't clean them as Emilie stated. I'm trying to call the method of prepareForReuse and 'cleaning' the cell, but I'm having trouble doing that. Here is my code:
- (void)prepareForReuse {
NSArray *viewsToRemove = [self.view subviews];
for (UILablel *v in viewsToRemove) {
[v removeFromSuperview];
}
Doing that, cleans even the selected cells label.
- (void)viewDidLoad {
self.sortedDictionary = [[NSArray alloc] initWithObjects:#"Californa", #"Alabama", #"Chicago", #"Texas", #"Colorado", #"New York", #"Philly", #"Utah", #"Nevadah", #"Oregon", #"Pensilvainia", #"South Dekoda", #"North Dekoda", #"Iowa", #"Misouri", #"New Mexico", #"Arizona", #"etc", nil];
self.rowSelection = -1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
customCell.title.text = [self.sortedDictionary objectAtIndex:indexPath.row];
return customCell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
CategorieCell *customCell = (CategorieCell *)[tableView cellForRowAtIndexPath:indexPath];
if (self.info) {
[self.info removeFromSuperview];
}
self.info = [[UILabel alloc] init];
[self.info setText:#"Hello"];
[self.info setBackgroundColor:[UIColor brownColor]];
CGRect labelFrame = CGRectMake(0, 0, 50, 100);
[self.info setFrame:labelFrame];
[customCell.infoView addSubview:self.info];
NSLog(#"%ld", (long)indexPath.row);
self.rowSelection = [indexPath row];
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([indexPath row] == self.rowSelection) {
return 159;
}
return 59;
}
The answer is quite simple : you reuse your cell like you should, but never clean them
Reusing your UITableViewCell means that the cell you clicked on previously will be reused when it will go off-screen.
When clicked, you add a view to your UITableViewCell. When reused, the view is still there because you never remove it.
You have two choices : One, you could set a tag of the self.info view (or check with the indexpath you're keeping in memory), then check when you dequeue the cell if the info view is there, and remove it. The cleaner solution would be to implement the view removal by overriding the prepareForReuse method of your custom UITableViewCell
Precision
The first thing you need to do is set a tag for your self.info view after initializing it:
[self.info setTag:2222];
If you want to keep it as simple as possible, you could check and remove the self.info view directly in your cellForRowAtIndexPath method :
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
customCell.title.text = [self.sortedDictionary objectAtIndex:indexPath.row];
if [customCell.infoView viewWithTag: 2222] != nil {
[self.info removeFromSuperview]
}
return customCell;
I am not a percent sure this code compiles, I cannot test it on my side for now. Hope it works !
I created a custom UITableViewCell class that embedded a UITextfield to each cell, in the addItemTableViewController, I want to get text values within all UITextField-embededd cells and create a new model object, but I'm running into a problem:
cellForRowAtIndexPath returns nil for invisible cells, after I scrolled down to the buttom of my tableview then hit the Add button, the first a few rows' textField text value became null.
Is there anyway I can fix this? I've been Googlging for hours and still not find a answer for it.
Here's my addItemTableViewController code:
- (IBAction)doneAdd:(UIBarButtonItem *)sender {
[self.delegate addItem:[self newItem]];
}
- (NSMutableArray *)newItem
{
NSMutableArray *newItem = [[NSMutableArray alloc] init];
for (int i = 0; i < [_appDelegate.title count]; i ++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
UPFEditableUITableViewCell *cell = (UPFEditableUITableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
NSLog(#"%#", cell.editField.text);
//[newItem addObject:cell.editField.text]; //this does not work as null cannot be added into a array
}
NSLog(#"%#", newItem);
return newItem;
}
Here's my custom UITableViewCell class implementation
#import "UPFEditableUITableViewCell.h"
#implementation UPFEditableUITableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.editField = [[UITextField alloc] initWithFrame:CGRectZero];
[self.contentView addSubview:self.editField];
}
return self;
}
- (void)layoutSubviews
{
if ([self.detailTextLabel.text length] == 0) {
self.detailTextLabel.text = #" ";
}
[super layoutSubviews];
// place the edit field in the same place as the detail text field, give max width
self.editField.frame = CGRectMake(self.detailTextLabel.frame.origin.x, self.detailTextLabel.frame.origin.y, self.contentView.frame.size.width-self.detailTextLabel.frame.origin.x, self.detailTextLabel.frame.size.height);
}
- (void)showEditingField:(BOOL)show
{
self.detailTextLabel.hidden = YES;
self.editField.text = self.detailTextLabel.text;
}
#end
I think made a fundamental mistake, have my view talks with the model layer, what a lesson learned...
anyway, I managed to work out a solution, in short, here's what I did:
made cell as the delegate of the UITextField
implemented textFieldDidChange, to capture textField changes, once there's a change, submit the changed content to the model
And here's the code:
in the cellForRowAtIndex:
[cell.editField addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
cell.editField.delegate = self;
and here's the code for the textFieldDidChange:
- (void)textFieldDidChange :(UITextField *)theTextField
{
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
[self.item removeObjectAtIndex:indexPath.row];
[self.item insertObject:theTextField.text atIndex:indexPath.row];
}
This is not a problem.The cell are dequeud and reused whenever new cells are created.Hence while scrolling the tableview at the top they become null and the new cells are created with the same identifier.
For your problem you will need to store the value of textfield's value into a dictionary.For this you will need to save it at the time you are dequeing the cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellReuseIdentifier = #"cellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellReuseIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellReuseIdentifier];
}else{
NSLog(#"text is %#",cell.textLabel.text);
for (UIView *v in cell.contentView.subviews) {
if ([v isKindOfClass:[UITextField class]]) {
UITextField *textField = (UITextField *)v;
[myDictionary setObject:textField.text forKey:indexPath]; // declare myDictionary in the interface first.This will also prevent the values from duplicating
NSLog(#"%#",myDictionary);
}
}
}
return cell;
}
To get value from UITextField you can set the delegate on your ViewController. Then you should implement textField:shouldChangeCharactersInRange:replacementString: where you can update NSString value.
The second solution might be keeping reference to the each cell in NSMutableArray.
Anyway you try to avoid calling cellForRowAtIndexPath: from table view controller.
You should always try to save the data in model classes and use the array of these model class instances to load the table. So that you don't need the tableCells to get the data after that. The datas are always to be fetched from models and not the UIs (TableCells in this case).
You might be loading the tablecell initially using an arra,y. If so, use that array to create the model class objects you mentioned instead of the tablecells.
I've got a table view where the cells are created as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.textLabel.text = #"TempTitle";
cell.textLabel.font = [UIFont fontWithName: #"AppleSDGothicNeo-Bold" size:16.0];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
UILabel *numberLabel = [[UILabel alloc] init];
[numberLabel setFrame:CGRectMake(230.0, 5.0, 40.0, 30.0)];
[numberLabel setText:[NSString stringWithFormat:#"%#", [self.arrayOne objectAtIndex:indexPath.row]]];
UIButton *buttonDown = [[UIButton alloc] init];
[buttonDown setFrame:CGRectMake(190.0, 5.0, 40.0, 30.0)];
buttonDown.tag = indexPath.row;
[buttonDown addTarget:self action:#selector(quantityDown:) forControlEvents:UIControlEventTouchUpInside];
UIButton *buttonUp = [[UIButton alloc] init];
[buttonUp setFrame:CGRectMake(270.0, 5.0, 40.0, 30.0)];
[cell addSubview:buttonDown];
[cell addSubview:numberLabel];
[cell addSubview:buttonUp];
return cell;
}
where self.arrayOne (name changed for this thread) holds integer values that are displayed in each cell. The method when buttonDown is selected is as following:
- (void) quantityDown:(id)sender {
int clicked = ((UIButton *)sender).tag;
... math to lower int by one and save the new value in arrayOne
... this part works just fine. The value does go down, as intended.
[self.tableView reloadData];
}
When the tableView reloads, a new label is printed on top of the existing label in that cell, as can be seen in the image below:
I can only assume that new buttons are being printed on top of the existing ones as well. This is both expensive and makes the numbers unreadable (especially if you change it multiple times!). Leaving the view and coming back to it shows the new numbers cleanly, but only until you start changing them again.
A similar effect happens when I use the UISegmentedControl at the top. Selecting one or the other changes the contents of the table and runs [self.tableView reloadData]. The textLabel for each cell reloads just fine when this method is called, but the sub views do not reload, and instead stack upon one another.
How would I go about writing this so that there is only ever one subview in each cell, instead of multiple stacked upon one another? Something speedy and not expensive on resources. Maybe removing all subviews and then adding the new ones? I tried something like
[[cell.contentView viewWithTag:tagVariable]removeFromSuperview];
to no avail. Really, I only need to modify the one cell in the table that the user is clicking in. Except for when the user uses the UISegmentedControl at the top, then all the cells need to be modified.
Thanks in advance.
EDIT 1:
Created a custom UITableViewCell class...
#interface SSCustomTableViewCell : UITableViewCell
#property (nonatomic) UILabel *quantity;
#property (nonatomic) UIButton *down;
#property (nonatomic) UIButton *up;
#end
#implementation SSWeightsTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.quantity = [UILabel new];
[self.contentView addSubview:self.quantity];
self.down = [UIButton new];
[self.contentView addSubview:self.down];
self.up = [UIButton new];
[self.contentView addSubview:self.up];
}
return self;
}
- (void) layoutSubviews {
[super layoutSubviews];
[self.down setFrame:CGRectMake(190.0, 5.0, 40.0, 30.0)];
[self.down setBackgroundColor:[UIColor purpleColor]];
[self.up setFrame:CGRectMake(270.0, 5.0, 40.0, 30.0)];
[self.up setBackgroundColor:[UIColor purpleColor]];
[self.quantity setFrame:CGRectMake(230.0, 5.0, 40.0, 30.0)];
[self.quantity setTintColor:[UIColor purpleColor]];
}
#end
and then in my UITableView class...
#import "SSCustomTableViewCell.h"
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView
registerClass:[SSCustomTableViewCell class]
forCellReuseIdentifier:NSStringFromClass([SSCustomTableViewCell class])
];
}
and
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier: NSStringFromClass([SSWeightsTableViewCell class])
forIndexPath:indexPath
];
}
but now all I see is the following...
where the subviews are not correctly laid out, nor are they the correct size. Also, within the cellForRowAtIndexPath method, I cannot access the cell's components. When I enter
cell.quantity
I get an error saying that "Property 'quantity' not found on object of type UITableViewCell*"
So I tried
SSCustomTableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier: NSStringFromClass([SSWeightsTableViewCell class])
forIndexPath:indexPath
];
and the error goes away, but it still looks the same when I run it.
Edit 2: Working Solution
All I had to do was add
cell = [[UITableViewCell alloc]init];
within the cellForRowAtIndexPath method. Everything else stayed the same, and no custom classes required.
You are seeing this error because cell instances are reused. If you add a view each time you configure the cell, you will be adding to cells that already have the subviews on them. You should create your own subclass of UITableViewCell and add the subviews to it in init. Then your method above would just set the values on the various subviews.
Your cell would look something like this:
#interface MyCustomCell : UITableViewCell
#property (nonatomic, strong) UILabel *myCustomLabel;
#end
#implementation MyCustomCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.myCustomLabel = [UILabel new];
[self.contentView addSubview:self.myCustomLabel];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
// Arrange self.myCustomLabel how you want
}
#end
Then your view controller would look like this:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView
registerClass:[MyCustomCell class]
forCellReuseIdentifier:NSStringFromClass([MyCustomCell class])
];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:NSStringFromClass([MyCustomCell class])
forIndexPath:indexPath
];
cell.myCustomLabel.text = #"blah";
return cell;
}
Try this in cell configure method, this will make sure objects doesn't overlap
for (UIView *subview in [cell.contentView subviews]) {
[subview removeFromSuperview];
}
example:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellId = #"cellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell==nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
}
for (UIView *subview in [cell.contentView subviews]) {
[subview removeFromSuperview];
}
return cell;
}
Hope this will help
I have a UITableView with 10 cells.Except the first cell, all are the same.
Around 3 cells are displayed on screen at a time. Each cell has a label which says "Claim". Depending on certain events, I change the "claim" in SOME cells to "claimed".
Problem is when I scroll the cells , the some other cells (whose "claim" I haven't changed to "claimed") also show as "claimed". This seems random and feel is due to cell reuse and poor implementation. Please review the code and help me approach this better.
My requirement is :
Display 10 cells out of which all are identical except the first one.
All identical cells have a button / label with text "claim"
When I press the button , "claim" should change to "Claimed" ONLY for that particular cell in which the button resides.
This change should persist event when I scroll.
Custom cell used is :
#import "CustomSaloonCell.h"
#implementation CustomSaloonCell
#synthesize claimButton;
#synthesize delegateListener;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.claimButton=[UIButton buttonWithType:UIButtonTypeSystem];
self.claimButton.frame=CGRectMake(30,140,80, 30);
[self.claimButton setTitle:#"claim" forState:UIControlStateNormal];
[self.claimButton setTitleColor:[UIColor purpleColor] forState:UIControlStateNormal];
self.claimButton.titleLabel.font = [UIFont fontWithName:#"Arial" size:(22)];
[self.claimButton addTarget:self action:#selector(claimButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:self.claimButton];
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)claimButtonPressed{
[self.delegateListener didClickedClaimButton:self];
}
#end
The cell creation function :
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row==0){
CustomHeaderCell *headerCell = [[CustomHeaderCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"header_cell"];
headerCell.selectionStyle = UITableViewCellSelectionStyleNone;
return headerCell;
}
static NSString *cellIdentifier = #"HistoryCell";
// Similar to UITableViewCell, but
CustomSaloonCell *cell = (CustomSaloonCell *)[theTableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[CustomSaloonCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.claimButton.tag = indexPath.row+TAG_OFFSET;
cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"salon.jpg"]];
cell.delegateListener = self;
return cell;
}
The delegate method which modifies the label is :
- (void) claimConfirmedDelegate:(NSInteger)tag{
CustomSaloonCell *selectedCell=(CustomSaloonCell*)[self.claimTableView cellForRowAtIndexPath: [NSIndexPath indexPathForRow:(tag-TAG_OFFSET)inSection:0]];
[selectedCell.claimButton setTitle:#"claimed" forState:UIControlStateNormal];
}
Save state of button (also title if your required) in separate instance mutable array.
When you pressed button and calling delegate method add that cell indexpath in your buttonStateArray.
Now check current indexPath in cellForRowAtIndexPath method: is containing buttonStateArray. If it present then change your button state and title yeah other thing if you want.
It will work after scrolling too.
Declare NSMutableArray *buttonStateArray; in .h file of tableview class.
Allocate it on initialization or after view loading.
- (void) claimConfirmedDelegate:(NSInteger)tag{
CustomSaloonCell *selectedCell=(CustomSaloonCell*)[self.claimTableView cellForRowAtIndexPath: [NSIndexPath indexPathForRow:(tag-TAG_OFFSET)inSection:0]];
[selectedCell.claimButton setTitle:#"claimed" forState:UIControlStateNormal];
[buttonStateArray addObject:[NSIndexPath indexPathForRow:(tag-TAG_OFFSET)inSection:0]];
}
Now in cellForRowAtIndexPath: method
for (NSIndexPath *selectedIndex in buttonStateArray){
if([selectedIndex isEqual:indexPath]){
//Change your state of button.
}
}