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.
Related
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.
I have an UITableView with custom cells and custom headers. When I move one cell upon editing, it pops up on to of the header view.
How can I keep the header view on top of all the cells?
The app uses storyboard, in case that makes a difference.
This is how it looks? https://www.dropbox.com/s/wg8oiar0d9oytux/iOS%20SimulatorScreenSnapz003.mov
This is my code:
[...]
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ListCell";
ListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
int handleSection = [self sectionToHandle:indexPath.section];
switch (handleSection)
{
case PrivateLists:
{
if (tableView.isEditing && (indexPath.row == self.privateLists.count))
{
cell.textField.text = NSLocalizedString(#"Lägg till ny lista", nil);
cell.textField.enabled = NO;
cell.textField.userInteractionEnabled = NO;
cell.accessoryType = UITableViewCellAccessoryNone;
cell.editingAccessoryView.hidden = YES;
}
else
{
List *list = [self.privateLists objectAtIndex:indexPath.row];
cell.textField.text = list.name;
cell.textField.enabled = YES;
cell.textField.userInteractionEnabled =YES;
cell.backgroundColor = [UIColor clearColor];
cell.onTextEntered = ^(NSString* enteredString){
list.name = enteredString;
UpdateListService *service = [[UpdateListService alloc]initServiceWithList:list];
[service updatelistOnCompletion:
^(BOOL success){
DLog(#"Updated list");
NSIndexPath *newPath = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
[self moveListToTop:list.ListId newIndexPath:newPath];
justMovedWithoutSectionUpdate = YES;
}
onError:
^(NSError *error){
[[ActivityIndicator sharedInstance] hide];
[[ErrorHandler sharedInstance]handleError:error fromSender:self];
}];
};
}
}
break;
default:
return 0;
break;
}
return cell;
}
-(UIView *)tableView:(UITableView *)aTableView viewForHeaderInSection:(NSInteger)section
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 22)];
UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, 300, 21)];
[textLabel setFont:[[AXThemeManager sharedTheme]headerFontWithSize:15.0]];
[textLabel setTextColor:[[AXThemeManager sharedTheme]highlightColor]];
[textLabel setText:#"SECTION TITLE"];
[textLabel setBackgroundColor:[UIColor clearColor]];
UIImageView *backgroundView = [[UIImageView alloc]initWithImage:[AXThemeManager sharedTheme].tableviewSectionHeaderBackgroundImage];
[backgroundView setFrame:view.frame];
[view addSubview:backgroundView];
[view sendSubviewToBack:backgroundView];
[view addSubview:textLabel];
return view;
}
- (float)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 22;
}
- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 44;
}
[...]
Good news! I was able to fix/workaround your problem in two different ways (see below).
I would say this is certainly an OS bug. What you are doing causes the cell you have moved (using moveRowAtIndexPath:) to be placed above (in front of) the header cell in the z-order.
I was able to repro the problem in OS 5 and 6, with cells that did and didn't have UITextFields, and with the tableView in and out of edit mode (in your video it is in edit mode, I noticed). It also happens even if you are using standard section headers.
Paul, you say in one of your comments:
I solved it badly using a loader and "locking" the table while
preforming a reloadData
I am not sure what you mean by "using a loader and locking the table", but I did determine that calling reloadData after moveRowAtIndexPath: does fix the problem. Is that not something you want to do?
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
//[self.tableView reloadData];
// per reply by Umka, below, reloadSections works and is lighter than reloadData:
[self reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationNone];
If you dont want to do that, here is another solution that feels a little hacky to me, but seems to work well (iOS 5+):
__weak UITableViewCell* blockCell = cell; // so we can refer to cell in the block below without a retain loop warning.
...
cell.onTextEntered = ^(NSString* sText)
{
// move item in my model
NSIndexPath *newPath = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
[self.itemNames removeObjectAtIndex:indexPath.row];
[self.itemNames insertObject:sText atIndex:0];
// Then you can move cell to back
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
[self.tableView sendSubviewToBack:blockCell]; // a little hacky
// OR per #Lombax, move header to front
UIView *sectionView = [self.tableView headerViewForSection:indexPath.section];
[self.tableView bringSubviewToFront:sectionView];
It's a bug.
You can quickly solve it by adding, after the line:
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newPath];
this lines:
UIView *sectionView = [self.tableView headerViewForSection:indexPath.section];
[self.tableView bringSubviewToFront:sectionView];
Not a solution but your code has number of issues. Who knows what happens if you fix them ;)
(1) Your cell may be nil after this line:
ListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
It should look like this:
ListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[ListCell alloc] initWithStyle:style
reuseIdentifier:cellIdentifier] autorelease];
}
(2) Two memory leaks in
-(UIView *)tableView:(UITableView *)aTableView viewForHeaderInSection:(NSInteger)section
->>Fix (When you add the label as subview it gets +1 ref).
UILabel *textLabel = [[[UILabel alloc] initWithFrame:CGRectMake(10, 0, 300, 21)] autorelease];
->>Fix (When you add the view as subview it gets +1 ref).
UIImageView *backgroundView = [[[UIImageView alloc]initWithImage:[AXThemeManager sharedTheme].tableviewSectionHeaderBackgroundImage] autorelease];
(3) Not a defect but this may help you. Try using this instead of [table reloadData]. It allows to animate things nicely and is not such a hardcore way to update the table. I'm sure it is much more lightweight. Alternatively try to look for other "update" methods. Given you don't delete rows in your example, something like [updateRowsFrom:idxFrom to:idxTo] would help.
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
float heightForHeader = 40.0;
if (scrollView.contentOffset.y<=heightForHeader&&scrollView.contentOffset.y>=0) {
scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
} else if (scrollView.contentOffset.y>=heightForHeader) {
scrollView.contentInset = UIEdgeInsetsMake(-heightForHeader, 0, 0, 0);
}
}
What I did to solve this problem was to set the zPosition of the view in the section header.
headerView.layer.zPosition = 1000 //just set a bigger number, it will show on top of all other cells.
I have a table view in which each cell contains a button. I dont use the didSelectRowAtIndexPath delegate but my custom method for the button action. I want that on clicking the button inside each cell, that cell should be highlighted or change colour and return to its normal state(colour) when the button in some other cell is clicked(making this cell highlighted).My button action method is:
- (void)SelectButtonTapped:(UIButton *)button
{
self.tabBarController.tabBar.userInteractionEnabled = YES;
AppDelegate *proDel = [[UIApplication sharedApplication]delegate];
UITableViewCell *cell = (UITableViewCell *)button.superview.superview;
//MyCustomCell *cell = (MyCustomCell *)button.superview.superview;
NSIndexPath *indexPath = [homeTable indexPathForCell:cell];
//cell.backgroundColor = [UIColor lightGrayColor];
//[homeTable setNeedsDisplayInRect:[homeTable rectForRowAtIndexPath:indexPath]];
DetailViewController *objDetail=[[DetailViewController alloc] init];
Home *tempSearchObj=(Home *)[profileArray objectAtIndex:indexPath.row];
objDetail.firstName=tempSearchObj.userName;
objDetail.userImageUrl=tempSearchObj.imageUrl;
objDetail.passedProfileID = tempSearchObj.profileID;
plsSelectLabel.text = [NSString stringWithFormat:#"%#",objDetail.firstName];
proDel.globalName = plsSelectLabel.text;
proDel.globalProfileId = objDetail.passedProfileID;
}
But this doesn't seem to work. Any help would be appreciated!!
Set the cell selection style in cellForRowAtIndexPath as :
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
[cell setSelectionStyle:UITableViewCellSelectionStyleBlue];
}
And :
yourButton.tag = indexPath.row;
Add the following line in your method to highlight the cell :
UIButton *clickButton = (UIButton *)sender;
int addButtonIndex = clickButton.tag;
NSIndexPath *indexPathHighlight = [NSIndexPath indexPathForRow:addButtonIndex inSection:0];
UITableViewCell *newCell = [yourTable cellForRowAtIndexPath:indexPathHighlight];
[newCell setSelected:YES animated:YES];
Unselect previous cell :
NSIndexPath *previousIndexPathHighlighted = [NSIndexPath indexPathForRow:previousTag inSection:0];
UITableViewCell *previousCell = [yourTable cellForRowAtIndexPath:previousIndexPathHighlighted];
[previousCell setSelected:NO animated:YES];
previousTag = clickButton.tag;
The proper way to go about this is that on your cellForRowAtIndex: method you have a way of knowing if your cell needs to look highlighted or not. Then, after clicking on a button and configuring whatever you need to do, you should simply do
NSArray *indexes = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:1]];
[self.tableView reloadRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationNone];
The other way to go about this, is to access the cell and change it directly. But not with button.superview.superview, but rather
UITableViewCell *cell = [self.tableView cellForRowAtIndex:[NSIndexPath indexPathForRow:0 inSection:1]];
I think the best way to do it is to make a
NSInteger selectedCellIndex;
Set the tags of your buttons in the cellForRowAtIndex method
button.tag = indexPath.row
Set the selectedCellIndex when you click the button and reload the table
selectedCellIndex = button.tag;
[self.tableView reloadData];
Then in the cellForRowAtIndex method check if the index is the same as the selectedCellIndex and add a different color to the cell.
if( indexPath.row == selectedCellIndex ){
//set different colors
}
Remember to do this outside the if( cell == nil ) check, otherwise it wouldn't work.
I have gesture recognisers set up on my table view.
Swipe to the right and the accessory changes to an image of a tick
Swipe to the left and is changes to a chevron image
If a cell is tapped, it loads a local HTML file.
If you swipe to the right, the tick appears as it should. However, if you then tap a cell to view a HTML file and come back to the table view, the image reverts to the chevron.
What's the best way to ensure the tick stays as it should?
EDIT
Further code:
From 'viewDidLoad':
UISwipeGestureRecognizer *recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSwipeRight:)];
[recognizer setDirection:(UISwipeGestureRecognizerDirectionRight)];
[self.tableView addGestureRecognizer:recognizer];
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSwipeLeft:)];
//recognizer.delegate = self;
[recognizer setDirection:(UISwipeGestureRecognizerDirectionLeft)];
[self.tableView addGestureRecognizer:recognizer];
- (void)handleSwipeLeft:(UISwipeGestureRecognizer *)gestureRecognizer
{
//Get location of the swipe
CGPoint location = [gestureRecognizer locationInView:self.tableView];
//Get the corresponding index path within the table view
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
//Check if index path is valid
if(indexPath)
{
//Get the cell out of the table view
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
//Update the cell or model
cell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"disclosure.png"]];
}
}
- (void)handleSwipeRight:(UISwipeGestureRecognizer *)gestureRecognizer
{
CGPoint location = [gestureRecognizer locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
if(indexPath)
{
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
// cell.accessoryType = UITableViewCellAccessoryCheckmark;
cell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"tick.png"]];
}
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"MFGCell";
MFGCell *cell = (MFGCell *) [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MFGCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.itemTitle.text = [item objectAtIndex:indexPath.row];
cell.itemDescription.text = [description objectAtIndex:indexPath.row];
cell.itemImageView.image = [UIImage imageNamed:[icons objectAtIndex:indexPath.row]];
return cell;
}
In reaction to the user's swipe you should store the user's choice (e.g. in a private instance variable of type NSMutableArray). When the user comes back to the table view you can then reuse the information in your tableView:cellForRowAtIndexPath: to setup the cell with the correct accessory style.
Property declaration:
#property(nonatomic, retain) NSMutableArray* _accessoryStyle;
Synthesize the property. Then add this snippet to the bottom of handleSwipeLeft: to store the user's choice:
- (void)handleSwipeLeft:(UISwipeGestureRecognizer *)gestureRecognizer
{
[...]
NSNumber* number = [numberWithInt:0];
[_accessoryStyle replaceObjectAtIndex:indexPath.row withObject:number];
}
Add a similar snippet to the bottom of handleSwipeRight::
- (void)handleSwipeRight:(UISwipeGestureRecognizer *)gestureRecognizer
{
[...]
NSNumber* number = [numberWithInt:1];
[_accessoryStyle replaceObjectAtIndex:indexPath.row withObject:number];
}
In tableView:cellForRowAtIndexPath::
NSString* accessoryImageName;
NSNumber* number = [_accessoryStyle objectAtIndex:indexPath.row];
switch ([number intValue])
{
case 0:
accessoryImageName = #"disclosure.png";
break;
case 1:
accessoryImageName = #"tick.png";
break;
default:
// replace with your error handling code
return nil;
}
cell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:accessoryImageName]];
For all this to work you need to initialize the _accessoryStyle array with the same number of elements that you expect your table view to have cells. For instance, in your view controller's viewDidLoad:
- (void) viewDidLoad
{
[super viewDidLoad];
self._accessoryStyle = [NSMutableArray arrayWithCapacity:0];
NSNumber* defaultAccessoryStyle = [numberWithInt:0];
int numberOfRows = 17; // get the real number from somewhere
for (int index = 0; index < numberOfCells; ++index)
[_accessoryStyle addObject:defaultAccessoryStyle];
}
And to balance this you need to add
- (void) viewDidUnload
{
[super viewDidUnload];
self._accessoryStyle = nil;
}
There is still much room for improvement:
Find better variable names
Use an enumeration for the different styles instead of just hardcoded numbers 0 and 1
Do not allocate a new UIImageView for each table view cell, just allocate two of them and use the right one depending on the accessory style
For your problem, there is an underlying logic issue because there is either a swipe left event firing when it should not or the views are just being unloaded and resetting to default. See if you can log when the events fire; otherwise the state of the view should be preserved. Also what I would do is add an extra state variable like int currentCellState that you change when you enter your different states to keep track of your states. Then in your viewDIdLoad make sure that all your data and your view are in sync, ie the value of currentCellState matches the state of your view.
The best way to do this is to put the images/buttons you have in an array, and each time the view loads it shows the item which index is selected..
in order to do this, the swipeMethode should be modified to something like this
-(void)swipeMethod: (UISwipeGestureRecognizer *) sender
{
if(sender.direction ==
UISwipeGestureRecognizerDirectionLeft && index < [myArray count]){
[self setSelectedIndex:index+1 animated:YES];
index++;
}else if (sender.direction == UISwipeGestureRecognizerDirectionRight && index > 0) {
[self setSelectedIndex:index-1 animated:YES];
index--;
}else {
return;
}
}
in the viewDidLoad add this code:
leftRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeMethod:)];
[leftRecognizer setDirection: UISwipeGestureRecognizerDirectionLeft];
[self.tableView addGestureRecognizer:leftRecognizer];
rightRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeMethod:)];
[rightRecognizer setDirection: UISwipeGestureRecognizerDirectionRight];
[self.tableView addGestureRecognizer:rightRecognizer];
Hey Simple little app for educational purposes, its a simple uitableview and I want users to be able to use a uislider to adjust the font size as needed. The code I have now works to change the font but only when the view is updated, ie when i pull up or down the table view to reveal other cells. I'd like the font change to be reflected immediately as a user moves the uislider if possible, here's the code that I have working half way:
-(UITableViewCell *) tableView: (UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *) indexPath {
static NSString *SimpleTableIdentifier = #"SimpleTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: SimpleTableIdentifier];
if (cell == nil)
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier: SimpleTableIdentifier] autorelease];
NSUInteger row = [indexPath row];
cell.text = [listData objectAtIndex:row];
UIImage *image = [UIImage imageNamed:#"star.png"];
cell.font = [UIFont systemFontOfSize:[fontSlider value]];
cell.image = image;
return cell;
}
You could make an IBAction for the fontSlider for the "Value Changed" option as follows:
-(IBAction) refreshResize
{
[self.view setNeedsDisplay];
}
That should work. setNeedsDisplay refreshes the screen.