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.
Related
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
I've been trying for a while now to get the tableview to work correctly. Every day I am making a bit of progress but I am not able to see any kind of feedback by testing the app so I am not sure if I am doing something correctly or not.
I've searched on google for a while, looked at apple docs, but I still don't understand how I can populate the UITableView properly.
First problem. Where do I get the cell? I need to make a custom cell to display the data. The solution seems to be using a nib, but how can I load it on another view controller?
Second problem. This cell should obviously have dynamic content. This content would be picked up from the user defaults (I am able to do that), but I have no idea how I could access the labels in the nibs to achieve this.
Third problem. I want to have different sections (each section has a different kind of cell to display), with different headers/footers. As always, here I find the documentation lacking. How can I achieve this?
Fourth, and most important problem. I have NO IDEA on how to load the cells. Apparently that is done by cellForRowAtIndexPath but I am unsure how it manages to do that.
Finally, here's the code (along with the stacktrace). Hope you can help me. Please, do not close this for duplicate. Firstly, it shouldn't be, secondly, it's not a duplicate if the duplicate question was asked 3+ years ago because of how much the libraries have been updated.
#import "profileViewController.h"
#import "../Objs/CCProfileObject.h"
#interface profileViewController ()
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (strong, nonatomic) IBOutlet UITableViewCell *proxySampleCell;
#property (strong, nonatomic) IBOutlet UITableViewCell *ccSampleCell;
#end
#implementation profileViewController
#synthesize tableView;
- (void)viewDidLoad{
// Dark mode, you can skip all of this
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(changeColor:)
name:#"darkToggle" object:nil];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if([userDefaults boolForKey:#"darkMode"] == YES){
[self changeColor:nil];
}
}
- (void)viewDidAppear:(BOOL)animated{
// Display profiles
NSData *arrayData = [[NSUserDefaults standardUserDefaults] objectForKey:#"profileArray"];
NSArray *profiles = [NSKeyedUnarchiver unarchiveObjectWithData:arrayData];
for(NSObject *profile in profiles){
if([profile class] == [CCProfileObject class])
{
CCProfileObject *ccProfile = (CCProfileObject*) profile;
printf("%s\n", [ccProfile.getName UTF8String]);
// Retrieve info from the object
// Duplicate cell
// Change appropriate labels
// Add cell to a specific section
}
// Different types of cells will be added later
// Concept is the same, what changes is the specific section and cell to duplicate
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
if(indexPath.section == 1){
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"reusableShippingCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"reusableShippingCell"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.textLabel.text = #"aldo";
return cell;
}
return nil;
}
// This should be the only part that actually works (I use the same class twice because proxy and shipping are not yet done)
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
// Display profiles
NSData *arrayData = [[NSUserDefaults standardUserDefaults] objectForKey:#"profileArray"];
NSArray *profiles = [NSKeyedUnarchiver unarchiveObjectWithData:arrayData];
NSMutableArray *sectionData = [[NSMutableArray alloc]init];
for(NSObject *profile in profiles){
// Credit
if([profile class] == [CCProfileObject class])
{
[sectionData insertObject:#1 atIndex:0];
}
// Shipping
else if([profile class] == [UIImage class])
{
[sectionData insertObject:#1 atIndex:1];
}
// Proxy
else if([profile class] == [UIImage class])
{
[sectionData insertObject:#1 atIndex:2];
}
}
int toReturn = 0;
for(int i = 0; i < [sectionData count]; i++){
toReturn += [[sectionData objectAtIndex:i] integerValue];
}
return toReturn;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
// Credit card section
if(section == 0){
return 1;
} else if (section == 1){
return 2;
} else {
return 3;
}
}
`Assertion failure in -[UITableView _configureCellForDisplay:forIndexPath:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3698.52.10/UITableView.m:9453
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView (<UITableView: 0x16106d000; frame = (0 0; 375 812); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x1d0243bd0>; layer = <CALayer: 0x1d0030f60>; contentOffset: {0, -88}; contentSize: {375, 117}; adjustedContentInset: {88, 0, 83, 0}>) failed to obtain a cell from its dataSource (<profileViewController: 0x15ff14c70>)
First throw call stack:
(0x1837f6d8c 0x1829b05ec 0x1837f6bf8 0x1841e6fa0 0x18d4975b0 0x18d4941e8 0x18d493e00 0x18d492b1c 0x18d48e668 0x18d3cb770 0x18796d25c 0x1879713ec 0x1878ddaa0 0x1879055d0 0x18d7b56b4 0x18379f2bc 0x18379ea7c 0x18379c7b0 0x1836bcda8 0x18569f020 0x18d69d78c 0x1048a4f90 0x18314dfc0)
libc++abi.dylib: terminating with uncaught exception of type NSException
You are not required to solve all of my problems with a single answer. Even fixing a problem/explaining what I need to do is enough as that will allow me to at least move a bit forward.
First, you don't need a custom nib for UITableViewCell unless you have a really complex cell. Rather than that, you can directly design your cell layout using Prototype Cells in UITableView's Attributes inspector. Just give Prototype Cells a number & design appropriate cell's layout inside UITableView view. Don't forget to set identifier for each of them, that will help you load correct layout in cellForRowAtIndexPath.
For simple cell layout, you can provide each of elements of the cell a tag number. This will help you access them whenever you need them like this:
UILabel *label = [cell viewWithTag:100];
Every UITableView needs a DataSource, you will implement UITableViewDataSource to provide:
Number of sections for your table
Number of items for each section
Content for individual cell
You just did it in your provided source code, except one thing: cellForRowAtIndexPath requires a UITableViewCell to be returned, not nil. If you return nil, exception will be thrown.
So, a working cellForRowAtIndexPath might look like this:
#implementation ViewController {
NSArray *data;
}
- (void)viewDidLoad {
[super viewDidLoad];
// You load your data from NSUserDefault here
data = [self loadDataFromNSUserDefault];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellIdentifier = #"defaultCell";
if (indexPath.section == 0) {
cellIdentifier = #"customCell1";
} else if (indexPath.section == 1) {
cellIdentifier = #"customCell2";
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
// Access your view's children using their `tag`
UILabel *title = [cell viewWithTag:100];
UIImageView *icon = [cell viewWithTag:101];
// Set content
title.text = [[data objectAtIndex:indexPath.row] objectForKey:#"title"];
icon.image = [UIImage imageNamed:[[data objectAtIndex:indexPath.row] objectForKey:#"icon"]];
return cell;
}
Your core problem lies in this line - if (indexPath.section == 1) - the section numbering starts from 0. So you return nil for cells in your first section (numbered 0), and any section beyond the second one (numbered 1), which causes the crash. You should never return nil from cellForRow.
That being said - I still recommend you read through the links provided in the comments, you may still learn something valuable and/or interesting.
I've encountered some very strange behaviour with my tableViewCells in a simple list application (iOS).
This is the basic function of my working app :
TableView with 2 sections. First section (e.g. with 2 cells) is always visible. Second section can be shown/hidden with a custom button in the section header. I've created two classes (i.e. FirstSectionItem and SecondSectionItem) both with a boolean property "completed" and some other properties.
After compilations the app runs as expected. Tapping cells results in showing the checkmarks and tapping them again hides the checkmarks (=custom imageView). However after tapping some different cells (random order) or showing/hiding the second section the checkmark (ImageViews) tend to stay checked no matter how much I tap the cells. After a while every cell is checked and can't be unchecked but the boolean values still keep changing.
Here's part of the code:
#property NSMutableArray *sectionTitleArray;
#property NSMutableDictionary *sectionContentDict;
#property NSMutableArray *firstSectionItems;
#property NSMutableArray *secondSectionItems;
ViewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
if (!self.sectionTitleArray) {
self.sectionTitleArray = [NSMutableArray arrayWithObjects:#"section1", #"section2", nil];
}
self.firstSectionItems = [[NSMutableArray alloc] init];
self.secondSectionItems = [[NSMutableArray alloc] init];
[self loadInitialData];
self.sectionContentDict = [[NSMutableDictionary alloc] init];
self.arraySection1 = [[NSMutableArray alloc] init];
for (int i = 0; i < [self.firstSectionItems count]; i++)
{
FirstSectionItem *firstSectionItem = [self.firstSectionItem objectAtIndex:i];
[self.arraySection1 addObject:firstSectionItem.itemName];
}
self.arraySection2 = [[NSMutableArray alloc] init];
for (int i = 0; i < [self.secondSectionItems count]; i++)
{
SecondSectionItem *secondSectionItem = [self.secondSectionItems objectAtIndex:i];
[self.arrayFuture addObject:secondSectionItem.itemName];
}
[self.sectionContentDict setValue:self.arraySection1 forKey:[self.sectionTitleArray objectAtIndex:0]];
[self.sectionContentDict setValue:self.arraySection2 forKey:[self.sectionTitleArray objectAtIndex:1]];
}
CellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ListPrototypeCell" forIndexPath:indexPath];
// ... cell content ...
NSArray *content = [self.sectionContentDict valueForKey:[self.sectionTitleArray objectAtIndex:indexPath.section]];
//... constraints ...
UIImage *checkmark = [UIImage imageNamed:#"checkmark.png"];
UIImage *noCheckmark = [UIImage imageNamed:#"transparent.png"];
UIImageView *imageView = [[UIImageView alloc] init];
if (indexPath.section==0){
FirstSectionItem *firstSectionItem = [self.firstSectionItems objectAtIndex:indexPath.row];
imageView.image = firstSectionItem.completed ? checkmark : noCheckmark;
}
if (indexPath.section==1){
if(self.sec2isTapped == YES){
SecondSectionItem *secondSectionItem = [self.secondSectionItems objectAtIndex:indexPath.row];
imageView.image = secondSectionItem.completed ? checkmark : noCheckmark;
}
}
[cell.contentView addSubview:imageView];
// ... more constraints
return cell;
}
didSelectRowAtIndexPath
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
if (indexPath.section ==0){
FirstSectionItem *tappedItem = [self.firstSectionItems objectAtIndex:indexPath.row];
tappedItem.completed = !tappedItem.completed;
}
if (indexPath.section ==1){
SecondSectionItem *tappedItem = [self.secondSectionItems objectAtIndex:indexPath.row];
tappedItem.completed = !tappedItem.completed;
}
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
[tableView endUpdates];
}
I've tried multiple approaches but none of them seem to work. I can't figure out what the problem is.
(I am new to objective C so some parts might seem a bit devious).
Thanks in advance.
The problem is that you are initializing the UIImageView each time cellForRowAtIndexPath is running. But remember that that cells are reused so what you are doing is reusing a cell that has a checkmark and adding a new clear checkmark on top.
What you need to do is either:
Add the imageView in the storyboard, tag it, and user the tag to find it in the code
Subclass UITableViewCell and assign the correct image in there.
There are MANY articles of how to do both online.
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 have dynamically tableView with custom cell. CustomCell .h file looks like this:
#property (strong, nonatomic) IBOutlet UILabel *uslugaName; //I set retain doesn't work too
#property (strong, nonatomic) IBOutlet UILabel *howMuchPayLbl;
My CellForRowAtIndexPathMethod:
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * cellIdentifier = #"Cell";
myCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
/*
if (!cell)
cell = [[myCustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
*/
if (indexPath.row !=15) {
cell.uslugaName.text =serviceNameArr[indexPath.row];
//окрашиваем ячейку в зависимости от активности услуги
if ([uslugaIsActiveArr[indexPath.row] isEqual: #"1"]) {
cell.backgroundColor = [UIColor blackColor];
cell.howMuchPayLbl.enabled = YES;
}
else {
cell.backgroundColor = [UIColor grayColor];
cell.howMuchPayLbl.enabled = NO;
}
if (![amountTmpArr[indexPath.row] isEqual: #"0"])
cell.howMuchPayLbl.text = [NSString stringWithFormat:#"Оплачиваю: %# KZT", amountTmpArr[indexPath.row]];
}
else {
cell.uslugaName.font = [UIFont fontWithName:#"System Bold" size:16];
cell.uslugaName.text = [NSString stringWithFormat:#"ОБЩАЯ СУММА ОПЛАТЫ: %#", fullAmount];
cell.howMuchPayLbl.hidden = YES;
}
return cell;
}
I want that last row different than others ( for this purpose this:
if (indexPath.row !=15)
). Problem is - when scrolling cell.howMuchPayLb disappear. If delete special code for last row - all works ok, why this happening?
Your code has an if else statement where one branch can set cell.howMuchPayLbl.hidden = YES; but the other branch does not set cell.howMuchPayLbl.hidden = NO;. So, once the label is hidden it will never be un-hidden. When the cell with the hidden label is reused the label remains hidden.
Add cell.howMuchPayLbl.hidden = NO; (and any other 'inverse' configuration required) to your if statement.
Refer This link will help you..
Its because of dequeueReusableCellWithIdentifier will not recognize the cell-identifire with same name.So you can use unique cell identifire like Cell1,Cell2...for each row..