Insert row in UiTableView does not update rest cell’s indexPath? - objective-c

Say i’m loading UITableview with each UITextView inside each cell as subview.And i’ve assigned indexPath.row as tags for each textview.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"userDetails";
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
UITextView *textView=[[UITextView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width, 60)];
NSString * myString = [contentArray1 objectAtIndex:indexPath.row];
textView.text= myString;
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
[tapRecognizer setNumberOfTouchesRequired:1];
[tapRecognizer setDelegate:self];
textView.userInteractionEnabled = YES;
textView.tag = indexPath.row;//assign tags to textview
[textView addGestureRecognizer:tapRecognizer];
[cell addSubview:textView];
return cell;
}
Below method gets called once the user taps on any textview.I’m seeing proper tag values printed when i tap on any textviews.
-(void) action:(id)sender
{
//NSLog(#"TESTING TAP");
UITapGestureRecognizer *tapRecognizer = (UITapGestureRecognizer *)sender;
NSLog (#"%d",[tapRecognizer.view tag]);
}
Now i would like to insert row in my tableview,say at index 3.
What i did is simple,
[contentArray1 insertObject:[NSString stringWithFormat:#"added cell”] atIndex:3];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:3 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
Now when i try to tap on any textviews after the inserted cell,i’m able to see the old tag values.Meaning,after row gets inserted to tableview at index=3,when i tap on textview i can see tag=2,then again when i tap on next cell’s textview i can see tag=2,it should be 3.
My question is,once we insert any row/cell in tableview,the tableview will not refresh other cell tags/index?….
I can fix it by calling reloadVisibleCells method.But i’m looking out for better solution.I don’t want to refresh whole screen just for inserting a row.Any solutions would be greatly appreciated.

Try to do it like this:
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];

Your problem is that inserting/deleting rows doesn't cause reloading of other rows, and this is the expected and correct behavior. However, since -tableView:cellForRowAtIndexPath: is not called for those other rows, they are still configured with old (now obsolete) tags.
You can fix it in a number of ways (off the top of my head):
subclass UITableViewCell and store the represented object itself as its property (instead of the object's index)
associate the object with the UITableViewCell with objc_setAssociatedObject()
use -[UITableView indexPathForCell:] instead of tags to figure out a cell's real index path.

Related

Xcode 9 - iOS 11 UITableView rows are empty

Ever since the new iOS 11 update, I have an app that will show blank tableview rows on the simulator and device. Even the row separators will not show for any of the rows that are supposed to be there. If I change the simulator to an older iOS version, the rows will show fine. No changes to code or storyboard.
The rows still have the data, meaning I can tap on one of the blank rows and it will execute the code and contain the information I was expecting.
It appears that other scenes that I have where the tableview is placed on a view and I don't use the built in text label work fine. Just this tableview class using the built in text label.
Here is my code for the tableview class...
#interface BunkPickTableViewController ()
#end
#implementation BunkPickTableViewController
#synthesize appDelegate, delegate, bunkPassedValue;
- (void)viewDidLoad {
[super viewDidLoad];
UIView *backgroundView = [[UIView alloc] initWithFrame:self.view.bounds];
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
CAGradientLayer *bgLayer = [BackgroundLayer tanGradient];
bgLayer.frame = self.view.bounds;
[self.view.layer insertSublayer:bgLayer atIndex:0];
self.tableView.backgroundView = backgroundView;
[self.navigationController.navigationBar setTintColor:[UIColor blackColor]];
self.title = #"Bunk Codes";
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[self navigationController] setNavigationBarHidden:NO animated:NO];
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [appDelegate.bunkArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.textLabel.textAlignment = NSTextAlignmentCenter;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.backgroundColor = [UIColor clearColor];
}
Bunk *bunkObj = [appDelegate.bunkArray objectAtIndex:indexPath.row];
cell.textLabel.text = bunkObj.bunkId;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
Bunk *bunkObj = [appDelegate.bunkArray objectAtIndex:indexPath.row];
[delegate dismissBunkPop:bunkObj.bunkId];
[self.navigationController popViewControllerAnimated:YES];
}
#end
TableView Settings Image
I had this issue as well with IOS 11 showing blank cells.. but in IOS 10 was fine. My issue was that I was using a gradient which for whatever reason stopped the cell text from being shown. Removing the gradient resolved the blank cells.
This is probably because your tableView gets under bgLayer.
The problem seems to be in self.view.layer insertSublayer:bgLayer atIndex:0. I was using insertSubview at index 0 and I had the same problem. This is probably a bug in iOS 11 - if your tableView is defined in storyboard, it always gets pushed to back.
The best solution is to put the bgLayer inside the tableView.backgroundView.
NOTE:
You could also solve it by calling sendSubviewToBack on the bgLayer in viewDidAppear, but unfortunetaly, tableview cells are moving to back on every tableview reloaddata, so it is not a good solution.
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = self.tableView.bounds;
gradient.colors = #[(id)[UIColor blackColor].CGColor, (id)[UIColor
grayColor].CGColor, (id)[UIColor
lightGrayColor].CGColor];
UIView *bgView = [[UIView alloc]
initWithFrame:self.tableView.bounds];
[self setBackgroundView:bgView];
[bgView.layer insertSublayer:gradient atIndex:0];
[self.tableView setBackgroundView:bgView];
Setting gradient layer for tableview's background view solved the problem for me. Upto iOS 10 we can directly insert sublayer to tableView but in iOS 11 layer should be inserted to UITableVIew's background view only.
In tandem with the aforementioned tableview subviews manipulation, this problem can also occur if your project existed pre Xcode 9 and you then checked Use Safe Area Layout Guides on your storyboard. Try unchecking that and see if it works.

NSTableViewCell selectedRow number for IBAction click

I'm running into a simple problem but have yet to find an optimal solution. I have a view based NSTableView that is loading it's cell views from different xibs. My table view is dynamic and based on user input I will dynamically add and remove rows ultimately adjusting the table data source. Each one of my NSTableCellViews have a button in it and I link the IBAction click handler to the NSView that holds the table view. What I need to do is get the row number for the button that was clicked in the table view so I can process the logic. I am able to do this successfully in : tableViewSelectionDidChange:(NSNotification *)notification
Here is how I do it:
- (void)tableViewSelectionDidChange:(NSNotification *)notification {
NSTableView *tableView = [notification object];
NSInteger selectedRow = [tableView selectedRow];
}
This works perfectly for a user actually clicking the row. Now when I move the NSButton IBAction and link it in the NSView as follows:
- (IBAction)buttonClickHandler:(NSButton *)sender {
NSInteger selectedRow = [self.tblView rowForView:sender];
NSLog(#"%ld", (long)selectedRow);
}
I based this approach from this selected answer.
I also tried this:
- (IBAction)buttonClickHandler:(NSButton *)sender {
id representedObject = [(NSTableCellView *)[sender superview] objectValue];
NSLog(#"%#", representedObject);
}
//My configuration
- (void)configureView {
[self.view setFrame:[self bounds]];
[self addSubview:self.view];
[self.view setWantsLayer:YES];
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
self.tblView.delegate = self;
self.tblView.dataSource = self;
[self.tblView setIntercellSpacing:NSMakeSize(0, 0)];
[self.tblView registerNib: [[NSNib alloc] initWithNibNamed:#"ParentCellXib" bundle:nil] forIdentifier:#"ParentCell"];
[self.tblView registerNib: [[NSNib alloc] initWithNibNamed:#"ChildCellXib" bundle:nil] forIdentifier:#"ChildCell"];
[self.tblView registerNib: [[NSNib alloc] initWithNibNamed:#"HeaderCellXib" bundle:nil] forIdentifier:#"HeaderCell"];
}
But the represented object returns null. If it's worth mentioning, I've set my File's Owner as the View that holds the tableView so I can link the IBAction and I've subclassed the TableCellView to a different class. However, I don't think this is part of the problem as far as I can see. Is there a simple solution to reliably give me the selectedRow number based on a button click in that cell? Both approaches I tried above return -1 and null respectively.
I would set the row in NSButton's tag property:
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
SomeTableCellView *cell = [tableView makeViewWithIdentifier:#"cell" owner:self];
if (cell == nil) {
cell = // init some table cell view
cell.identifier = #"cell";
}
cell.button.tag = row;
[cell.button setTarget:self];
[cell.button setAction:#selector(buttonAction:)];
}
- (IBAction)buttonAction:(id)sender {
NSLog(#"row: %d", sender.tag);
}
Try This
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
yourCustomeCell *aCell;
NSString *aStrIdentifier = #"yourIdentiFier";
aCell = (yourCustomeCell *)[tableView dequeueReusableCellWithIdentifier:aStrIdentifier];
//you have to set your indexpath
objc_setAssociatedObject(aCell.btnUpload_or_Add, #"objBtn", indexPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[aCell.YourButton addTarget:self action:#selector(yourButtonActiontapped:) forControlEvents:UIControlEventTouchUpInside];
return aCell;
}
-(IBAction)yourButtonActiontapped:(UIButton *)sender{
NSIndexPath *aIndPath = objc_getAssociatedObject(sender, #"objBtn");
NSLog(#"row:%#",aIndPath.row);
}
also you have to import #import <objc/runtime.h>
another way to get row in IBAction is TAG but objc is better option insted of TAG.
Create a subclass of UIButton and add a property for NSIndexPath for the button. Use this button in cellForRowAtIndexPath method. assign the index path of the cell to that of index path of the button.
On Tap, get the index path from its sender. In your case index path of that button.

How to remove previous subview and add new subview to UITableviewCell

I have created cell that can be expanded and collapsed, when the cell is expanded I add 2 subviews and remove those 2 subviews when cell is collapsed. Look at the code:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(selectedIndex == indexPath.row){
selectedIndex = -1;
UITableViewCell *cell = [self.tblView cellForRowAtIndexPath:indexPath];
[[cell viewWithTag:TAG_KHMER] removeFromSuperview];
[[cell viewWithTag:TAG_KOREAN] removeFromSuperview];
//[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tblView beginUpdates];
[self.tblView endUpdates];
return;
}
if(selectedIndex >= 0){
NSIndexPath *previousPath = [NSIndexPath indexPathForRow:selectedIndex inSection:0];
selectedIndex = indexPath.row;
UITableViewCell *cell = [self.tblView cellForRowAtIndexPath:previousPath];
[[cell viewWithTag:TAG_KHMER] removeFromSuperview];
[[cell viewWithTag:TAG_KOREAN] removeFromSuperview];
//[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
VocabularyController *vc = [self.vocabularyInfo objectAtIndex:indexPath.row];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
UILabel *khmerLabel = [[UILabel alloc] init];
khmerLabel.text = vc.khmer;
khmerLabel.font = [UIFont fontWithName:#"Hanuman" size:17];
[khmerLabel setNumberOfLines:0];
khmerLabel.tag = TAG_KHMER;
khmerLabel.frame = CGRectMake(20, 45, 300, 300);
UILabel *koreanPro = [[UILabel alloc] init];
koreanPro.text = vc.korean;
[koreanPro setNumberOfLines: 0];
koreanPro.tag = TAG_KOREAN;
koreanPro.frame = CGRectMake(20, 315, 300, 300);
[cell addSubview:khmerLabel];
[cell addSubview:koreanPro];
selectedIndex = indexPath.row;
//[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tblView beginUpdates];
[self.tblView endUpdates];
}
What happened is the cell seem not remove the previous one. it displays new text on the old text but when I click on the same cell twice again and then the cell can render the text good.
Can anyone help me how to display it properly.
After click twice times on the cell.
Don't try and add subviews like that - it's going to lead to confusion, because as you've found out UITableView recycles the cells.
Instead, create your own custom UITableViewCell subclass that can be switched between the various states you require, and has all the subviews already set up. You can do this in a number of ways - if you're using storyboards you can use prototype cells, or you can use a NIB, or you can create your custom subclass entirely in code (whichever you are most comfortable with).
Basically, don't add subviews to your cells in your table view delegate/datasource calls. Create a custom subclass, and you'll find everything much, much easier.

UITableView overlaps on scrolling

In my Storyboard I've got a table view.
I'm filling that table view using data loaded from a JSON file (loaded in viewDidLoad).
In my UITableView I did set "Prototype Cells" to 1 so I can easily select a Accessory.
My Prototype Cell has a Seque to a new View which needs to show the details of the selected item.
I'm filling my Cells programmatically using this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *videoTableIdentifier = #"VideoCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:videoTableIdentifier];
if (cell == nil)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:videoTableIdentifier];
UILabel * title = [[UILabel alloc] initWithFrame:CGRectMake(40,5, 240,20)];
[cell addSubview:title];
[title setText:[[videos objectAtIndex:indexPath.row] objectForKey:#"title"]];
UILabel * detail = [[UILabel alloc] initWithFrame:CGRectMake(40,28, 100,10)];
[cell addSubview:detail];
[detail setText:[NSString stringWithFormat:#"Year: %#", [[videos objectAtIndex:indexPath.row] objectForKey:#"year"]]];
[cell addSubview:[[UIImageView alloc] initWithImage:[UIImage imageNamed:[[videos objectAtIndex:indexPath.row] objectForKey:#"image"]]]];
return cell;
}
When I start scrolling the following happens:
So I started searching for a solution and found that I had to set: dequeueReusableCellWithIdentifier:videoTableIdentifier to "nil".
While I did that, this solved my problem, BUT now me Prototype Cell Accessory and Seque are gone so I can't navigate to the next view anymore.
I couldn't find a solution for this on the internets so I decided to ask this.
Would be awesome if someone could help.
Your problem is that every time you re-use a cell, you are adding new subviews to it, so if you have a cell with some labels, and you reuse it and add more labels, all of them appear overlaped.
I suggest you to make your own custom UITableViewCell with labels on it, and just change the value of the labels every time.
if (!title){
title = [[UILabel alloc] initWithFrame:CGRectMake(40,5, 240,20)];
[cell addSubview:title];
}
[title setText:[[videos objectAtIndex:indexPath.row] objectForKey:#"title"]];
Previous answer author is right. It could be better to use your own subclass of UITableViewCell.
I don't see any memory release. Are you using automatic reference counting?

Setting rowHeight in UITableView in viewDidAppear

This is the current working code.
But this should be simpler, I guess.
If I use the commented code I get a wrong height size.
Anyone some suggestions?
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
TableViewCell *cell = (TableViewCell *)[[self tableView]dequeueReusableCellWithIdentifier:CellClassName];
// TableViewCell *cell = [[[TableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellClassName] autorelease];
if (!cell)
{
NSArray *topLevelItems = [cellLoader instantiateWithOwner:self options:nil];
cell = [topLevelItems objectAtIndex:0];
// cell = [[[TableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellClassName] autorelease];
}
NSLog(#"cell height %f", cell.bounds.size.height);
[[self tableView] setRowHeight:cell.bounds.size.height] ;
}
Yes, you can definitely simplify it. Just call your own implementation of -tableView:cellForRowAtIndexPath: and use the cell that you get back from that, i.e.
UITableView *table = self.tableView;
NSIndexPath *index = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *cell = [self tableView:table cellForRowAtIndexPath:index];
[table setRowHeight:cell.bounds.size.height]
Voilà: no more duplicated code.
On another note, you might want to do this in -viewDidLoad or -viewWillAppear: if your view controller is going to appear with animation. Doing it in -viewDidAppear: will cause the table’s row height to visibly jump when the view-presentation animation finishes.