I have certain cells/rows on my tableview that should be set to 'selected' when the view is opened. In my code below, if the data source contains a specific user id, a green check mark should appear (this part works), and the row should be 'Selected'. That said, the selected state doesn't seem to work. How can I set a cell as selected?
ViewController.m
-(UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
NSDictionary *client = self.sectionClients[indexPath.section][indexPath.row];
static NSString *ClientTableIdentifier = #"ClientTableViewCell";
ClientTableViewCell *cell = (ClientTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:ClientTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"ClientTableViewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSString *photo = client[#"client photo"];
cell.clientName.text = [NSString stringWithFormat:#"%# %#", client[#"first name"], client[#"last name"]];
cell.subtext.text = client[#"phone"];
NSURL *imageUrl = [NSURL URLWithString:photo];
[cell.clientPhoto setImageWithURL:imageUrl];
if (self.selectionData != NULL && [[self.selectionData valueForKey:#"clients ids"] containsString:client[#"nid"]])
{
cell.greenCheck.image = [UIImage imageNamed:#"added.png"];
[cell setSelected:YES];
}
return cell;
}
Calling setSelected on a cell only sets the selected state for that cell, not for a row in the table. The selected state for a cell simply affects its appearance. It does not affect the selection state of the row itself.
Since cells can be re-used, your cellForRowAtIndexPath function should also clear the selected state if the cell is being used for a row that should not be selected.
In order to put a row into the selected state, you will also need to call selectRowAtIndexPath for each row that you want to be selected, even ones that are not currently displayed. So you should not call it from cellForRowAtIndexPath because that will only select rows that have actually been displayed.
I am not sure when the best time would be to call selectRowAtIndexPath. It would have to be after the table view has called numberOfRowsInSection for each section in the table. You can try selecting the rows from viewDidAppear. If the table is not ready at that time, then you may have to keep track of when numberOfRowsInSection has been called for every section.
Lots to discuss, but to give you a couple ideas...
First, you need to inform the table view that a row is selected using selectRowAtIndexPath.
One way to do that is to pass the row(s) you want pre-selected to the table view controller and then selecting the rows in viewDidLayoutSubviews. Note that viewDidLayoutSubviews is called many times throughout the controller's life-cycle, so you'll only want to do this once.
Also, you want to make sure you are tracking the selected state in your data source.
Assuming we pass an array of NSNumber:
#interface ClientTableViewController : UITableViewController
#property (strong, nonatomic) NSArray *preSelectedRows;
#end
and using a very simple object model like:
#interface ClientObject : NSObject
#property (strong, nonatomic) NSString *name;
#property (assign, readwrite) BOOL selected;
#end
you would set the .selected property in viewDidLoad():
// set .selected property for rows we're going to pre-select
for (NSNumber *n in _preSelectedRows) {
clientData[[n integerValue]].selected = YES;
}
and then, in viewDidLayoutSubviews:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// viewDidLayoutSubviews is called many times during controller lifecycle
// so we only want to do this ONCE
if (_preSelectedRows) {
for (NSNumber *n in _preSelectedRows) {
NSIndexPath *p = [NSIndexPath indexPathForRow:[n integerValue] inSection:0];
[self.tableView selectRowAtIndexPath:p animated:NO scrollPosition:UITableViewScrollPositionNone];
}
// clear the array so we don't call this again
_preSelectedRows = nil;
}
}
You can also save yourself a lot of effort by having your cell class handle the visual representation. Something like this:
#interface ClientTableViewCell()
{
UIImage *selectedImg;
UIImage *unselectedImg;
}
#end
#implementation ClientTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
selectedImg = [UIImage systemImageNamed:#"checkmark.square"];
unselectedImg = [UIImage systemImageNamed:#"square"];
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
self.textLabel.textColor = selected ? [UIColor redColor] : [UIColor blackColor];
self.imageView.image = selected ? selectedImg : unselectedImg;
}
#end
and your cellForRowAt becomes simply:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ClientTableViewCell *cell = (ClientTableViewCell *)[tableView dequeueReusableCellWithIdentifier:clientTableIdentifier forIndexPath:indexPath];
// we only need to set the name, because the cell class will handle
// visual appearance when selected
cell.textLabel.text = clientData[indexPath.row].name;
return cell;
}
Here is a complete example: https://github.com/DonMag/PreSelectExample
Related
I have a TableView with customCell, that has next properties:
#interface CustomTableViewCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UIImageView *image;
#property (weak, nonatomic) IBOutlet UILabel *nameOfImage;
#property (weak, nonatomic) IBOutlet UIButton *start;
#property (weak, nonatomic) IBOutlet UIButton *stop;
#property (weak, nonatomic) IBOutlet UIProgressView *progressView;
#property (weak, nonatomic) IBOutlet UILabel *realProgressStatus;
In table view - when user press start button - an image is downloaded, as well as progressView with realProgressStatus reflects current situation. At this stage everything works perfect, but when scroll table view and return back to the cell - that was already fulfilled with data - all info disappeared(except the nameOfImage, I set it separately).
I implemented next method in my CustomTableViewCell class:
-(void)prepareForReuse
{
[super prepareForReuse];
self.progressView.progress = 0.1;
self.realProgressStatus.text = #"";
self.image.image = [UIImage imageNamed:#"placeholder"];
}
I implemented new property NSMutableSet self.tagsOfCells, where I save number of cells where images where already downloaded.
I tried to make changes in TableView method but effect is the same :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* PlaceholderCellIdentifier = #"Cell";
CustomTableViewCell *customCell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier forIndexPath:indexPath];
customCell.delegate = self;
customCell.cellIndex = indexPath.row;
if (!customCell)
{
customCell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"cell"];
}
else
{
NSNumber *myNum = [NSNumber numberWithInteger:indexPath.row];
if (![self.tagsOfCells containsObject:myNum])
{
NSLog(#"Row: %ld",(long)indexPath.row);
UIImage *img = [UIImage imageNamed:#"placeholder"];
customCell.realProgressStatus.text = #"";
customCell.progressView.progress = 0.1;
customCell.image.image = img;
[customCell setNeedsLayout];
}
}
if ( indexPath.row % 2 == 0 )
customCell.backgroundColor = [UIColor grayColor];
else
customCell.backgroundColor = [UIColor darkGrayColor];
customCell.nameOfImage.text = self.names[indexPath.row];
return customCell;
}
EDIT:
I populate self.tagsOfCells in one of methods during downloading images, here is the method:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
self.customCell.realProgressStatus.text = #"Downloaded";
UIImage *img = [UIImage imageWithData:self.imageData];
self.customCell.image.image = img;
self.customCell.tag = self.selectedCell;
[self.savedImages setObject:img forKey:self.customCell.nameOfImage.text];
NSNumber *myNum = [NSNumber numberWithInteger:self.selectedCell];
[self.tagsOfCells addObject:myNum];
}
Thanks in advance for any help or advice.
Content-related actions shouldn't occur in the prepareForReuse function.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewCell_Class/#//apple_ref/occ/instm/UITableViewCell/prepareForReuse
If a UITableViewCell object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from the UITableView method dequeueReusableCellWithIdentifier:. For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state. The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell. If the cell object does not have an associated reuse identifier, this method is not called. If you override this method, you must be sure to invoke the superclass implementation.
Edited
Alfie is right, prepareForReuse isn't invoked after returning from cellForRowAtIndexPath
First thing I would try is edit the code
if (!customCell)
{
customCell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"cell"];
}
else
{
NSNumber *myNum = [NSNumber numberWithInteger:indexPath.row];
if (![self.tagsOfCells containsObject:myNum])
{
NSLog(#"Row: %ld",(long)indexPath.row);
UIImage *img = [UIImage imageNamed:#"placeholder"];
customCell.realProgressStatus.text = #"";
customCell.progressView.progress = 0.1;
customCell.image.image = img;
[customCell setNeedsLayout];
}
}
to the following
if (!customCell)
{
customCell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"cell"];
}
NSNumber *myNum = [NSNumber numberWithInteger:indexPath.row];
if (![self.tagsOfCells containsObject:myNum])
{
NSLog(#"Row: %ld",(long)indexPath.row);
UIImage *img = [UIImage imageNamed:#"placeholder"];
customCell.realProgressStatus.text = #"";
customCell.progressView.progress = 0.1;
customCell.image.image = img;
[customCell setNeedsLayout];
}
This really isn't an expensive operation and it's better to have this logic in a single place. Especially since you got rid of prepareForReuse, this should be changed.
The reason when you scroll away, and then come back to not find your data is cellForRowAtIndexPath is being called again, which results in the default data overriding the new data you just added.
You would want to have an underlying data structure and retrieve data from there.
What I would do:
For instances, you can keep a mutable list as such
NSMutableArray* tableData = [[NSMutableArray alloc] init];
add image data for each row to the array
[tableData add:imageData];
and then display it add cellForRowAtIndexPath
customCell.image.image = [tableData objectAtIndex:[indexPath row]];
This would be the normal practice for populating a UITableView with data. This way, even if you scroll away tableData would remain the same, resulting in consistent data.
Note
I would also call new data asynchronously using NSURLSession from within the cell if I had to implement this. Just a suggestion=)
I don't think this has anything to do with your use of prepareForReuse. A couple things look fishy.
You're setting the delegate and cellIndex properties on your cell before you know whether it's nil or not. If it's nil then these properties will never be set.
I also don't think you need the else clause in there. The logic that it wraps doesn't have anything to do with whether a cell could be dequeued. it has to do with configuring a non-nil cell.
A missing piece from your question is where do you populate the tagsOfCells object? That code is important. If you're not setting the tag in cellForRowAtIndexPath it seems like your data will get out of sync and this will cause your cell to be misconfigured.
Try the following code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* PlaceholderCellIdentifier = #"Cell";
CustomTableViewCell *customCell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier forIndexPath:indexPath];
if (!customCell)
{
customCell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"cell"];
}
NSNumber *myNum = [NSNumber numberWithInteger:indexPath.row];
if (![self.tagsOfCells containsObject:myNum])
{
NSLog(#"Row: %ld",(long)indexPath.row);
UIImage *img = [UIImage imageNamed:#"placeholder"];
customCell.realProgressStatus.text = #"";
customCell.progressView.progress = 0.1;
customCell.image.image = img;
[customCell setNeedsLayout];
}
customCell.delegate = self;
customCell.cellIndex = indexPath.row;
customCell.nameOfImage.text = self.names[indexPath.row];
if (indexPath.row % 2 == 0)
{
customCell.backgroundColor = [UIColor grayColor];
}
else
{
customCell.backgroundColor = [UIColor darkGrayColor];
}
return customCell;
}
Edit
Regarding info in another answer here. prepareForReuse is not being called after you configure your cell. It's being called when you invoke dequeueReusableCell and hence before you configure your cell. I don't believe prepareForReuse is the problem.
It looks like you are not using interface builder's prototype cells so you are using the wrong dequeue reusable cell method. The correct one in your situation does not have an index path param e.g.
CustomTableViewCell *customCell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier];
And as the others said you need to move your 2 lines of initialization after you alloc init your custom cell because it's nil before then. Eg you need to dequeue the cell and if nil create one. Then set your params on the cell now you have a valid instance.
My project runs on XCode7 with CoreData and fetchedResultsController
There's a UILabel in the UITableViewCell, when i clicked "+" to create a new Item and the UILabel shows the name.
But the name(Green label) doesn't appear very first time i run the app and add insert the the item. Not sure what's the reason?
Here are the code and picture:
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSIndexPath *indexPathT = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
EventMO *event = [_fetchedResultsController objectAtIndexPath:indexPathT];
TargetCell * cell = (TargetCell *)[tableView dequeueReusableCellWithIdentifier:#"TargetCell"];
cell.name.text = event.name;
if ([cell isKindOfClass:[TargetCell class]]) {
cell.contentView.backgroundColor = [UIColor yellowColor];
}
DLog(#"%# ------ %u",cell.contentView.subviews,indexPath.row);
cell.name.backgroundColor = [UIColor greenColor];
return cell;
}
logout:"
<UILabel: 0x7d8f8bc0; frame = (-42 -21; 42 21); text = '123'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; tag = 10; layer = <_UILabelLayer: 0x7d8f8ae0>>"
) ------ 0
TargetCell.h/.m
#interface TargetCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *name;
#end
#import "TargetCell.h"
#implementation TargetCell
- (void)awakeFromNib {
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
By guess is that tableview is reusing cells and sometimes it reuses the one that doesn't contain blue label. Can you post full cellForIndex path method?
My problem is i have a AddEventViewController controller which present from HomeViewController. After fill the event names etc, then save directly by [context save:&error] and then [self.dismissViewController: addEventViewController]; cased the issue happened. I guess maybe UITableView is not ready to update
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
After i change to order of dissmissViewController and [context save:&error], everything works like normal.
I have been experiencing this event where UITableView cell contents are loaded fine on iOS 9, but not on iOS 8. I have verified this both on the simulator and on the device.
I have tried loading the data on viewDidLoad, viewWillAppear, and even in viewDidAppear but still the same. I have tried a dispatch_async to reload the the tableView but still the same.
Check the images below to see the problem I am experiencing.
However, when I tap the position for the stars, the UIView for the stars suddenly appears.
Here is my code for the UITableView controller.
#interface EvalTableViewController ()
#property (strong, nonatomic) NSDictionary *allItems;
#property (strong, nonatomic) NSArray *items;
#property (strong, nonatomic) NSMutableArray *ratings;
#end
#implementation EvalTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedRowHeight = 140.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
_allItems = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"AssessmentItems" ofType:#"plist"]];
_items = nil;
_items = [_allItems objectForKey:_currentEval];
[self loadRatings];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"itemCellId";
ItemTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
cell.item.text = _items[indexPath.row];
cell.ratingView.value = ((NSNumber *)_ratings[indexPath.row]).floatValue;
[cell bringSubviewToFront:cell.ratingView];
[cell.ratingView addTarget:self action:#selector(updateRating:) forControlEvents:UIControlEventValueChanged];
[cell layoutSubviews];
return cell;
}
- (void)updateRating:(HCSStarRatingView *)ratingView {
long indexOfRatingView = [self.tableView indexPathForCell:(ItemTableViewCell *)[[ratingView superview] superview]].row;
[_ratings replaceObjectAtIndex:indexOfRatingView withObject:[[NSNumber alloc] initWithFloat:ratingView.value]];
[self.tableView reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:indexOfRatingView inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
}
- (void)loadRatings {
_ratings = nil;
_ratings = [[NSMutableArray alloc] init];
for (int i = 0; i < _items.count; i++) {
[_ratings addObject:[[NSNumber alloc] initWithFloat:1.0]];
}
}
}
I have not added anything on the ItemTableViewCell code. Here it is:
#implementation ItemTableViewCell
- (void)awakeFromNib {
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
I am new on iOS app development, I hope you can help.
Thanks.
It seems that all our past efforts to solve my problem was more focused on loading the cells, and not on the UITableViewCell itself.
I have found out that the problem lies on my constraints for my label and for the star rating. I just added a height constraint to my star rating and it becomes visible when the tableView appears. I also needed to put [self.tableView reloadData]; on my viewDidAppear method.
However, the UITableViewCell displays in its fixed height initially, then adjusts it's height after the view is presented to the user which might not be appealing for the user. And the tableView scrolls when I tap the star rating.
But anyways, this gave me hope in solving my problem.
I just find it odd. Because, if it's a constraint or layout error, why does it work fine on iOS 9 and not on iOS 8?
Thanks a lot.
Try adding this code in the awakefromnib method in the tableViewCell.m file(after importing HCSStarRatingView ).Also try the debugging mode in simulator to see if the view is there before the first touch.
HCSStarRatingView *starRatingView = [[HCSStarRatingView alloc] initWithFrame:initWithFrame:CGRectMake(0, 0, self.ratingView.frame.size.width, self.ratingView.frame.size.height)];
I guess there is problem with bring subview to front may be.
please try this in cell for row at index path method .
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"itemCellId";
ItemTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
cell.item.text = _items[indexPath.row];
cell.ratingView.value = ((NSNumber *)_ratings[indexPath.row]).floatValue;
//[cell bringSubviewToFront:cell.ratingView];
[cell.ratingView addTarget:self action:#selector(updateRating:) forControlEvents:UIControlEventValueChanged];
[cell layoutSubviews];
return cell;
}
And if it is still not working than in view did load add this code and test again.
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.estimatedRowHeight = 140.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
_allItems = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"AssessmentItems" ofType:#"plist"]];
_items = nil;
_items = [_allItems objectForKey:_currentEval];
[self.tableView reloadData];
[self loadRatings];
}
Hope this will solve your problem.
After calling [self loadRatings]; , you need to reload your table data try it.
I want to change the selected cell data permanently as i have done in my didSelectRowAtIndexPath method but the problem is that when I select a row the cell data is change but when i select any other row the previous become as it was, and I also want to save rows in an array, those been selected in an array. here is my code right now.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
#try {
static NSString *cellidentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellidentifier];
if(cell == nil)
{
NSArray *cellObjects = [[NSBundle mainBundle] loadNibNamed:#"Cell" owner:self options:nil];
cell = (UITableViewCell*) [cellObjects objectAtIndex:0];
}
UILabel *label;
long row = [indexPath row];
label = (UILabel *)[cell viewWithTag:10];
label.text =time[row];
label.textColor = [UIColor blackColor];
cell.imageView.image = [img_clock_blue objectAtIndex:indexPath.row];
cell.backgroundColor = [UIColor yellowColor];
[tableView setSeparatorInset:UIEdgeInsetsZero];
//int hecValue;
return cell;
}
#catch (NSException *exception)
{
NSLog(#"%#",exception);
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView reloadData];
UITableViewCell *cell1 = (UITableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
cell1.imageView.image = [UIImage imageNamed:#"1_red.png"];
cell1.backgroundColor = [UIColor clearColor];
}
You're modifying the cell, which is a bad idea. You need to modify the place where it's getting its data.
in your didSelectRowAtIndexPathjust find the objectAtIndex:in the array, modify it to your will, then reload the table.
If you only have, for example, titles (NSStrings), then an array of strings will suffice. But most of the time it won't, because you're displaying something custom.
it looks like you don't have a custom class here, so I'll just make an example that you can translate easily. Let's say you're tryign to display a list of Animal objects.
Create your Animal class inheriting from NSObject. (New file, class, and so on).
Add the properties you will need in the Animal.h file, for example
#property (weak, nonatomic) NSString *name;
#property (nonatomic) int size;
#property (nonatomic) int weight;
#property (weak, nonatomic) NSString *countryOfOrigin;
You'll also technically need a class to create/manage/fetch/save these Animal objects but let's keep it simple and do it in the viewDidLoad of your controller.
- (void)viewDidLoad{
[super viewDidLoad];
Animal *myAnimal = [[Animal alloc]init];
myAnimal.name = #"Lion";
myAnimal.size = 13;
myAnimal.weight = 100;
myAnimal.countryOfOrigin = #"NoIdeaHahahah";
// You can hardcode a couple like that, and add them to your array used for your tableview data. Basically we just want some of your custom objects in an array, for your tableview.
}
Ok so now we have an array of Animal (our data) for your tableview. You can use that to create your rows.
When creating the cell in the cellForRow, simply start with :
Animal *animal = [myArray objectAtIndex:indexPath.row];
and then feed your cells with the properties of that animal
cell.titleLabel.text = animal.name;
for example.
And in the didSelect you can modify that specific animal, like I said at the very beginning of this answer :)
Animal *animal = [myArray objectAtIndex:indexPath.row];
animal.name = #"IJustChangedTheName";
[self.tableView reloadData];
All this is common practice, except what we did in the viewDidLoad that is very brutal, but I'm sure you'll be able to adapt that to your code :)
Try this,
create a NSMutableArray #property in view controller. lets say selectedIndexArray
initialize the array in viewDidLoad by self.selectedIndexArray = [[NSMutableArray alloc] init];
in cellForRowAtIndexPath method
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//other codes
if ([self.selectedIndexArray containsObject:indexPath]) {
cell.imageView.image = [UIImage imageNamed:#"1_red.png"]; //assumes all selected cells have same image
} else {
cell.imageView.image = [img_clock_blue objectAtIndex:indexPath.row];
}
.....//other code
}
in didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.selectedIndexArray addObject:indexPath];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
The code for setting up cell contents should all be in cellForRowAtIndexPath:.
You should create a real data model to represent the contents of your cells instead of the time array. Create an array of custom objects (or dictionaries) with properties such as "time" and "selected". Use indexPath.row to find the correct object and then use its "selected" property to decide which kind of image to give it.
didSelectRowAtIndexPath: sets "selected" YES or NO and doesn't need to change the cell at all.
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.