I'm developing an application on iOS targeting iOS 7.0 and later.
Now, my question is that how to reload the rows of a UITableView which are visible on screen. I'm using dynamic cells.
Actually while selecting an option I'm changing some colors like title color of each cell but those cells which are already loaded on screen are not changed and if I'm loading whole table then its taking a long time.
Waiting for your kind reply.
Code
//on clicking this button color is updating
- (IBAction)btnDone:(id)sender {
appDel.selectedMood=_moodDetView.str;
appDel.barColor=_moodDetView.backgroundColor;
self.navigationController.navigationBar.barTintColor = _moodDetView.backgroundColor;
//[self.mainTblView reloadData];
[self.menuView setBackgroundColor:appDel.barColor];
[_moodDetView setHidden:YES];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//For menu contents
if (tableView.tag==2) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
cell.textLabel.textColor = [UIColor whiteColor];
cell.textLabel.font = [UIFont fontWithName:#"Futura" size:14.0];
cell.textLabel.textAlignment = NSTextAlignmentCenter;
cell.backgroundColor = [UIColor clearColor];
}
switch (indexPath.row) {
case 0:
{
cell.textLabel.text=#"Select Mood";
}
break;
case 1:
{
cell.textLabel.text=#"Search by Author";
}
break;
case 2:
{
cell.textLabel.text=#"Search by Category";
}
break;
case 3:
{
cell.textLabel.text=#"Favorites";
}
break;
case 4:
{
cell.textLabel.text=#"Feeds";
}
break;
default:
break;
}
return cell;
}
//for main table
else{
// Configure the cell...
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
FeedCustomCell* CELL=( FeedCustomCell *)cell;
CELL.cellImageView.layer.cornerRadius=CELL.cellImageView.frame.size.width/2;
CELL.cellImageView.clipsToBounds=YES;
CELL.gesRec=[[CustomTapGestureRecognizer alloc]initWithTarget:self action:#selector(authorBtnTouched:)];
[CELL.lblAuthName setTextAlignment:NSTextAlignmentJustified];
[CELL.lblAuthName setTextColor:[AppDelegate invertColor:appDel.barColor]];
NSString * str=[NSString stringWithFormat:#"%#",[[dataArray objectAtIndex:indexPath.row] objectForKey:#"authorId"]];
UIImage * img=[UIImage imageNamed:str];
CELL.cellImageView.image=img?img:[UIImage imageNamed:#"n3"];
[CELL.lblAuthName addGestureRecognizer:CELL.gesRec];
CELL.gesRec.authorImage=CELL.cellImageView.image;
NSString * authName=[[dataArray objectAtIndex:indexPath.row] objectForKey:#"authorName"];
[CELL.lblAuthName setText:authName];
[CELL.gesRec setAuthorId:(int)str.integerValue];
[CELL.gesRec setAuthorName:authName];
[CELL.txtViewQuote setTextColor:appDel.barColor];
CELL.txtViewQuote.text=[[dataArray objectAtIndex:indexPath.row] objectForKey:#"quoteTxt"];
CGSize sizeThatShouldFitTheContent = [CELL.txtViewQuote sizeThatFits:CELL.txtViewQuote.frame.size];
CELL.heightConstraintOfTxtView.constant = sizeThatShouldFitTheContent.height;
[CELL.contentView sizeToFit];
return CELL;
}
}
regards:
Syed Meesum Ali
Junior Software Developer
The simplest possible way to do it would be:
[myTable reloadRowsAtIndexPaths:myTable.indexPathsForVisibleRows withRowAnimation:UITableViewRowAnimationAutomatic];
However since you are looking for a more optimized way, The following would be better:
On button click, in the target method, iterate over visible cells, and make relevant changes in each cell.
- (IBAction)btnDone:(id)sender {
for (UITableViewCell* cell in yourTable.visibleCells) {
if ([cell isKindOfClass:[FeedCustomCell class]]) {
[((FeedCustomCell*)cell).lblAuthName setTextColor:[AppDelegate invertColor:appDel.barColor]];
}
}
}
This should be way faster that reloading (drawing) all visible cells.
ALSO, a word of advice. Use different reuseIdentifers for different types of cells, or your table might cause app crashes.
If you implement custom cell subclassing UITableViewCell, you can implement prepareForReuse method. You can reset some properties example, alpha, selection state in that method.
Related
I have a custom UITableViewCell named as Hobbies.
Everything is working fine.Except one UIIssue.
When user taps on any cell I want to change the text colour of that particular Cell .And when user select another I want the previous selected cell should return to its original state.
Currently I am able to change the colour on Cell select but not able to revert it back when user selects another.
Here is the code I am using to change the textColor of a particular cell:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
HobbiesCell *cell = (HobbiesCell*)[tableView cellForRowAtIndexPath:indexPath];
cell.dateLabel.textColor = [UIColor colorWithWhite:255 alpha:0.5f];
}
How can I revert It back when user selects another cell.
You can create an object of UITableViewCell say previousCell and assign it each time you select one. This will be your last selected cell and you can assign it the default color each time you click a new cell.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
HobbiesCell *cell = (HobbiesCell*)[tableView cellForRowAtIndexPath:indexPath];
previousCell.dateLabel.textColor = [UIColor blackColor]; //Assuming that this is the color you want to go back
cell.dateLabel.textColor = [UIColor colorWithWhite:255 alpha:0.5f];
previousCell = cell;
}
declare this variable
int selectedIndex;
in your cellForRowAtIndexPath
if(indexPath.row == selectedIndex)
{
cell.dateLabel.textColor = [UIColor colorWithWhite:255 alpha:0.5f];
}
else
{
cell.dateLabel.textColor = [UIColor colorWithWhite:0 alpha:0.5f];//your default cell color
}
and in your didSelectRowAtIndex
selectedIndex = indexPath.row;
[tableView reloadData];
You should always keep in mind that the cells are reusable, so the one you change will be used as is for displaying other rows when you scroll.
Instead, you should keep an array of your own models that keep the data (in your case color information) and use the cells only for displaying it.
To revert the color simply keep a reference to the latest NSIndexPath.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyRowModel *prevRowModel = [self.rowModels objectAtIndex:self.lastIndexPath.row];
prevRowModel.color = [UIColor colorWithWhite:255 alpha:1f];
MyRowModel *rowModel = [self.rowModels objectAtIndex:indexPath.row];
rowModel.color = [UIColor colorWithWhite:255 alpha:0.5f];
[self.tableView reloadRowsAtIndexPaths:#[indexPath, self.lastIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
self.lastIndexPath = indexPath;
}
If you go for reload the tableview then try this .
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// reload the table
[tableView reloadData];
HobbiesCell *cell = (HobbiesCell*)[tableView cellForRowAtIndexPath:indexPath];
previousCell.dateLabel.textColor = [UIColor colorWithWhite:255 alpha:0.5f]; // color insert which you want to insert
}
hope it helps you without adding varible.
Try cell.textLabel.highlightedTextColor.
if (cell == nil) {
........
/* your cell initiation code */
........
cell.textLabel.textColor = [UIColor whiteColor];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.highlightedTextColor = [UIColor orangeColor];
}
You need to maintain a reference to the currently selected indexPath. Something like...
#property (nonatomic, strong) NSIndexPath *currentHobby;
Then in your didSelectRowAtIndexPath: method insert this...
if(_currentHobby && ![_currentHobby isEqual:indexPath]) {
HobbiesCell *currentCell = [tableView cellForRowAtIndexPath:_currentHobby];
currentCell.dateLabel.textColor = [UIColor originalColor];
}
_currentHobby = indexPath;
Then in your cellForRowAtIndexPath: make sure you include...
if([indexPath isEqual:_currentHobby]) {
cell.dateLabel.textColor = [UIColor selectedColor];
} else {
cell.dateLabel.textColor = [UIColor originalColor];
}
I'm attempting to put an instance of UIDatePicker in the accessory view of a UITableView cell, and have been following this SO thread as a template. However, it looks as if the picker is being placed above the cell entirely:
Below is the code I'm using to try to add a Date Picker to the accessory view of a UITableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellNewRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"RowCell";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
cell.backgroundColor = [UIColor colorWithRed:0.922 green:0.937 blue:0.949 alpha:1];
switch (indexPath.row) {
case EmployeeOvertimeRow:
cell.textLabel.text = NSLocalizedString(#"Test", #"One");
_datePicker = [[UIDatePicker alloc]init];
_datePicker.datePickerMode = UIDatePickerModeTime;
//OLD: cell.accessoryView = _datePicker;
//POST EDIT
[cell.contentView addSubview:_datePicker];
break;
default:
break;
}
return cell;
}
Does anyone have any guidance on what I'm doing wrong or how to fix this?
The accessoryView of a UITableViewCell is surely not what you think : it's the little view at the right of the cell, typically, it's an arrow, and it's pretty small, not made to be the width of the screen at all. You should try adding your view to the contentView. You will need to set a bigger height for your cell in the heightForRowAtIndexPath method, too.
I have 2 table views I want to customise the cell style of the second view depending on the selected row in the first table view. it works when in customising the accessories but it does't for the style. am using iOS 7.1 and Xcode 5.1
as i using the following code
thanks in advance
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *TableIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TableIdentifier];
}
cell.detailTextLabel.textColor = [UIColor lightGrayColor];
cell.textLabel.text = [array objectAtIndex:indexPath.row];
if ([selectedRow isEqualToString:#"Cars"]) {
[cell setAccessoryType: UITableViewCellAccessoryDetailButton];
}
if ([selectedRow isEqualToString:#"Houses"]) {
NSArray *temp = [[dic objectForKey:selectedRow]valueForKey:#"Job"];
cell.selectionStyle = UITableViewCellStyleSubtitle;
cell.detailTextLabel.adjustsFontSizeToFitWidth = YES;
cell.detailTextLabel.text = [temp objectAtIndex:indexPath.row];
}
if ([selectedRow isEqualToString:#"Libraries"]) {
cell.selectionStyle = UITableViewCellStyleValue1;
}
return cell;
}
You can't change a cell's style without creating a new cell.
The line:
cell.selectionStyle = UITableViewCellStyleValue1;
makes no sense. You are trying to change the cell's selection style by passing in a enum value for a cell's style. That's two different concepts.
When you want to update a cell's style (not the selection style), you need to call UITableView initWithStyle:reuseIdentifier: again passing in the desired style.
This may be a long question in order I'll be able to explain all the problem I've encountered.
So, I want to implement such UI functionality:
So I have a UITableView which is implemented in one file and it's cells which is implemented in other file TableViewCell.m
And I need after clicking on button read more to expand UILabel with text mesage as on the second screen and after clicking on close message button to restore UILabel and consequently resize TableView cell (this is one button i only change images of it). So in order to resize UILabel I use [UILabel sizeToFit] and change label.numberOfLines from 3 to 0 (iOS 6 feature as far as I know).And here code of creating cells:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
CPNTalkCell *cell = (CPNTalkCell *)[tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
NSString *modelId = [[models objectAtIndex:indexPath.row] valueForKey:#"modelId"];
NSDictionary *model = [[models objectAtIndex:indexPath.row] valueForKey:#"model"];
return [self setupCell:cell forModel:model withId:modelId];
}
In setupCell method I do additional adjustments and in CPNTalkCell the cell is described vi IB.
And here the code of button ReadMore event handler where I try to resize cell and label:
-(void)ResizeLabel:(id)sender
{
UIView* sender_button = (UIView*)sender;
UIButton* _resizingSenderButton = (UIButton*)sender;
CGRect _button_before = _resizingSenderButton.frame;
NSIndexPath* indexPath = [listView indexPathForCell:(UITableViewCell*)[[ sender_button superview]superview ]]; //In such an ugly way may be i
//access NSIndexpath of the current cell from
// which button was clicked
CPNTalkCell *cell = (CPNTalkCell*)[listView cellForRowAtIndexPath:indexPath];
if (cell.messageLabel.numberOfLines==3) {
[self.listView beginUpdates];
cell.messageLabel.numberOfLines=0;
[cell.messageLabel sizeToFit];
_button_before.origin.y = cell.messageLabel.frame.origin.y+ cell.messageLabel.frame.size.height+7;
_resizingSenderButton.frame=_button_before;
[_resizingSenderButton setBackgroundImage:[UIImage imageNamed:#"readmore2.png" ] forState:UIControlStateNormal];
[cell sizeToFit];
// [self.listView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[self.listView reloadData];
[self.listView endUpdates];
}
else
{
cell.messageLabel.numberOfLines = 3;
[self.listView beginUpdates];
[cell.messageLabel sizeToFit];
CGRect _button_after = _resizingSenderButton.frame;
_button_after.origin.y = cell.messageLabel.frame.origin.y+ cell.messageLabel.frame.size.height+7;
_resizingSenderButton.frame=_button_after;
[_resizingSenderButton setBackgroundImage:[UIImage imageNamed:#"readmore1.png" ] forState:UIControlStateNormal];
[cell sizeToFit];
[self.listView reloadData];
//[self.listView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[self.listView endUpdates];
}
}
So this code work and after clicking cells resize to fit the text and it's length but works strange - sometimes buttons disappear or appear on the text after scrolling the list buttons also may disappear or even text.I use reloadData when I change cell - i know it's not optimal, but as you see I've commented code of reloading of one cell because it works even more strange or not from the first clicking.
I also overload heightForRowatIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *modelId = [[models objectAtIndex:indexPath.row] valueForKey:#"modelId"];
NSDictionary *model = [[models objectAtIndex:indexPath.row] valueForKey:#"model"];
CPNTalkCell *cell = [_cells objectForKey:modelId];
if (cell == nil) {
cell = [self setupCell:nil forModel:model withId:modelId];
}
return cell.rowHeight;
}
But in it i describe initial cell parameters before clicking on a button via calling of rowHeight described in TalkCell file!
I know it's a long question and I sometimes explain not all clear but I think iOS experts will be able to understand my problem and propose solution of this problem. I really need a help because I try to solve this problem not for a one day.Great thanks in advance!
You need to overload heightForRowAtIndexPath to return the appropriate value based on the expanded cell
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *mode
lId = [[models objectAtIndex:indexPath.row] valueForKey:#"modelId"];
NSDictionary *model = [[models objectAtIndex:indexPath.row] valueForKey:#"model"];
CPNTalkCell *cell = [_cells objectForKey:modelId];
if (cell == nil) {
cell = [self setupCell:nil forModel:model withId:modelId];
}
if ( indexPath == expandedIndexPath )//save the expanded cell's index path somewhere
{
return expandedCellHeight;
}
return cell.rowHeight;
}
Tried my hardest to find the answer here first, but I'm stuck. I have a UITableView set with UITableViewStyleGrouped with 4 sections, each with one or two rows. In two of the sections I needed a row to be a larger height to hold the content I'm sticking in there.
Looks nice except when I scroll up and down, textLablels, accessories and extra subviews start to shift into different rowss and I can't figure out why.
This screenshot shows the table view when it loads and then after I scroll up and down a few times. Each time I scroll different row content shuffles.
I thought I read something about this being an issue with the grouped style. Sure enough, I don't see this issue if I change the table style to default. Am I not allowed to dynamically set the height for some rows when using the grouped style?
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == kSection_Info && indexPath.row == kSectionRow_InfoPhoto)
{
return 84.0;
}
else if (indexPath.section == kSection_Level && indexPath.row == kSectionRow_LevelLevel)
{
return 70.0;
}
return 44.0;
}
I'm setting up each row manually in celForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"RecipientEntryCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
switch (indexPath.section)
{
case kSection_Info:
{
switch (indexPath.row)
{
case kSectionRow_InfoName:
{
cell.textLabel.text = #"Name";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(74, 8, 195, 25)];
self.nameLabel.textAlignment = UITextAlignmentRight;
self.nameLabel.font = [UIFont systemFontOfSize:16];
self.nameLabel.textColor = [UIColor blueColor];
self.nameLabel.text = self.currentRecipient.fullName;
[cell.contentView addSubview:self.nameLabel];
break;
}
case kSectionRow_InfoPhoto:
{
cell.textLabel.text = #"Photo";
self.imageButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.imageButton.frame = CGRectMake(10, 14, 64, 64);
[self.imageButton addTarget:self action:#selector(onImageButtonTouch:) forControlEvents:UIControlEventTouchUpInside];
NSString *imageName = #"add_image.png";
UIImage *thumb = [UIImage imageNamed:imageName];
[self.imageButton setImage:thumb forState:UIControlStateNormal];
cell.accessoryView = self.imageButton;
break;
}
default:
{
break;
}
}
break;
}
case kSection_List:
{
switch (indexPath.row)
{
case kSectionRow_ListHasList:
{
cell.textLabel.text = #"Is Listed";
self.listSwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
cell.accessoryView = self.listSwitch;
break;
}
case kSectionRow_ListBudget:
{
cell.textLabel.text = #"List Amount";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
self.budgetLabel = [[UILabel alloc] initWithFrame:CGRectMake(124, 8, 145, 25)];
self.budgetLabel.textAlignment = UITextAlignmentRight;
self.budgetLabel.font = [UIFont systemFontOfSize:16];
self.budgetLabel.textColor = [UIColor blueColor];
self.budgetLabel.text = [#"$" stringByAppendingFormat:#"%0.2f", [self.currentRecipient.budget floatValue]];
[cell.contentView addSubview:self.budgetLabel];
break;
}
default:
{
break;
}
}
break;
}
case kSection_Level:
{
switch (indexPath.row)
{
case kSectionRow_LevelLevel:
{
self.levelSlider = [[UISlider alloc] initWithFrame:CGRectMake(8, 2, 284, 40)];
self.levelSlider.minimumValue = 0.0;
self.levelSlider.maximumValue = 100.0;
self.levelSlider.continuous = YES;
UIImage *meterImage = [UIImage imageNamed:#"meter_labels.png"];
UIImageView *meterView = [[UIImageView alloc] initWithFrame:CGRectMake(8, 32, 284, 24)];
[meterView setImage:meterImage];
[cell.contentView addSubview:self.levelSlider];
[cell.contentView addSubview:meterView];
[meterImage release];
break;
}
case kSectionRow_LevelHasLevel:
{
cell.textLabel.text = #"Show Level";
self.levelSwitch = [[[UISwitch alloc] initWithFrame:CGRectZero] autorelease];
cell.accessoryView = self.levelSwitch;
break;
}
default:
{
break;
}
}
break;
}
case kSection_RecipientDelete:
{
cell.textLabel.text = #"Delete Recipient";
cell.textLabel.textAlignment = UITextAlignmentCenter;
cell.textLabel.textColor = [UIColor blueColor];
break;
}
default:
{
break;
}
}
}
return cell;
}
The "content shuffling" you see is most likely due to improper handling of cell re-use.
There is no issue specifically with the grouped style. The problem is more likely to manifest itself in that style because fewer cells fit in the screen which requires more scrolling and requires more cell re-use.
You are setting up the cell contents only when creating cells (when cell == nil). When a cell scrolls off the screen, it goes into the re-use queue. The row at the other end that is now visible re-uses the cell view that is in the re-use queue. The re-used cell contains the contents of some other row.
When all the cells are alike (at least in regard to the UI controls and not the data), this isn't a problem. When all or some of the cells are different, you get controls appearing where you don't expect them.
Because you only have a small number of rows and each one is layed out differently, the quick (and perhaps dirty) solution is to use a different re-use identifier for each cell like this:
NSString *CellIdentifier =
[NSString stringWithFormat:#"RecipientEntryCell-%d-%d",
indexPath.section, indexPath.row];
This is not at all recommended if the table view is going to have lots of different cells since every cell for every row in the table will be in memory at the same time (instead of just the few on the screen). Do not use a unique identifier as a way to solve any and all cell re-use problems.
The Table View Programming Guide shows an alternate way to design table views like this where you have a few cells with different layouts. See "The Technique for Static Row Content" on this page.
Ultimately, it's better if you understand that the table view re-uses cells for good reasons and not try to workaround it all the time. Generally, the cellForRowAtIndexPath should look like this:
static NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCell...
if (cell == nil) {
//Create cell...
//Set UI content that applies to all rows (if any)...
}
else {
//Cell is being re-used.
//Remove UI content that doesn't apply to this row...
}
//Add UI content that applies only to this row...
//Copy values from data source to cell UI controls...
return cell;
If you construct the cells as shown above, do not maintain class-level references to UI controls inside cells (like nameLabel, imageButton, etc). Instead, the control values should be set in cellForRowAtIndexPath from a backing data variable (model) and the value should be read or saved back to the backing data variable as soon as the UI control changes.
For example, instead of storing a reference to a UISlider in a cell, store the current slider value as a float ivar. Initialize the float where appropriate (eg. viewDidLoad). In cellForRowAtIndexPath, create the UISlider, set its value using the float ivar, and have the slider call a method when its value changes. In that method, copy the slider's value to the float ivar.
Hope this helps.