UITableViewCell's textLabel.text is being partially substituted by ellipsis - iphone

I have a UITableView with several UITableViewcells (UITableViewCellStyleSubtitle). When the user taps on one of cells, I push a detail VC that allows the user to change the text of the cell. When I pop this ViewController, the text on the cell appears abbreviated with an ellipsis:
Before:
After:
After changing the text to "Wartyrl" and popping the detail VC.
If I tap on any other cell, the ellipsis disappears and the correct text is displayed. What might be causing this?
This is the code that changes the text of the cell (via a delegate method):
#pragma mark - FRREditTaskViewControllerDelegate
-(void) editTaskViewController:(FRREditTaskViewController *)sender
didChangeNameForTask:(FRRFlatTask *)aTask{
NSIndexPath *idx = [NSIndexPath indexPathForRow:[self.pendings indexOfObject:aTask] inSection:0];
[self.tableView cellForRowAtIndexPath:idx].textLabel.text = aTask.name;
}
BTW, this code is called before popping the detail VC and while the UITableViewController is still hidden. I don't know if this is relevant.

When the cell is created, the UILabel is only large enough to handle the initial text (it doesn't dynamically resize). Calling [self.tableView reloadData] after changing the text (end of your didChangeNameForTask method) should make it display properly.

You can call [cell.textLabel sizeToFit].
In many cases it's cleaner to instead update your internal data and call reloadRowsAtIndexPaths:withRowAnimation:. That way you don't have so many pieces monkeying with the implementation details of the cell (when you change your mind on how to lay it out). It also ensures that your real data matches the cell.
Even better in most cases is to create a UITableViewCell subclass called TaskTableViewCell. You hand it a Task and it observes the Task with KVO, managing its own layout (including calling sizeToFit when needed). That way you don't need delegate methods to tell you when it updates. When the task changes, the cell automatically updates itself. This moves all the layout issues into the cell class and out of the datasource.

It seems that the textLabel hesitates to resize its width. If its too adamant to change, just reload the tableView directly using [tableView reloadData].
If you don't want to reload the entire tableView, you can reload just a particular cell like that following.
NSIndexPath *idx = [NSIndexPath indexPathForRow:[self.pendings indexOfObject:aTask] inSection:0];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:idx] withRowAnimation:UITableViewRowAnimationFade];
This will show a nice fade animation when changing the textLabel's text of the cell.

-(void) editTaskViewController:(FRREditTaskViewController *)sender
didChangeNameForTask:(FRRFlatTask *)aTask{
NSIndexPath *idx = [NSIndexPath indexPathForRow:[self.pendings indexOfObject:aTask] inSection:0];
UITableViewCell *thiscell=[self.tableView cellForRowAtIndexPath:idx];
thiscell.textLabel.text =[nsstring stringwithFormat:#"%#",aTask.name];
[self.tableView reloadRowsAtIndexPaths: [NSArray arrayWithObject:idx] withRowAnimation:NO];//if u dont want to use reload data then use like this or use
//- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
}

Related

Implementing Prev./Next buttons in UITableView to make first responder a UITextField in a non-visible cell

in my app I have a UITabeView with UITextFields inside every cell. However I'm having troubles implementing previous/next buttons to make the text field inside the previous/next UITableViewCell be the first responder.
I've subclassed the UITableViewCell class to have it call a certain method of it's delegate when the prev./next buttons are pressed and to pass the cell itself as a parameter of this method (so I can get its index path to calculate which is the index path of the cell whose text field has to be made first responder)
In the implementation of the delegate's method I:
get the index path of the cell whose button was pressed
add or subtract 1 from the index path of that cell (depending on which button was pressed)
get the cell whose textfield has to be made first responder using the -cellForRowAtIndexPath: method on the table view
make the text field first responder
Problem is that the -cellForRowAtIndexPath: method returns the cell only if it is visible. So when the cell is not visible it will return nil and the above algorithm will not work while when the cell is on screen it will work correctly.
Here is my code for the prev. button, provided that MUInfoMateriaTableViewCell is my subclass of UITableViewCell and that it has a textField property that returns its text field:
- (void)prevButtonPressedInCell:(MUInfoMateriaTableViewCell *)cell
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
NSIndexPath *previousIndexPath = [NSIndexPath indexPathForRow:indexPath.row-1 inSection:indexPath.section];
MUInfoMateriaTableViewCell *newCell = (MUInfoMateriaTableViewCell *)[self.tableView cellForRowAtIndexPath:previousIndexPath];
[newCell.textField becomeFirstResponder];
}
Is there any way to "get" a cell that is not visible so I can make its text field the first responder? Or can you suggest me another algorithm to work around this problem?
You can fix this with a multi-step process:
keep track of the cell that contains the desired text field to make first responder
calculate the NSIndexPath of the cell to be shown and call [self.tableView scrollToRowAtIndexPath:atScrollPosition:animated:] to bring it into view
implement tableView:willDisplayCell:forRowAtIndexPath: to call becomeFirstResponder on the desired cell when it becomes visible and it matches the desired cell or index path
The last step is important because calling becomeFirstResponder doesn't do anything if the receiver isn't a subview of any window.
Make the cell visibile by scrolling the table view, using scrollToRowAtIndexPath:atScrollPosition:animated:.
For instance
- (void)prevButtonPressedInCell:(MUInfoMateriaTableViewCell *)cell
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
NSIndexPath *previousIndexPath = [NSIndexPath indexPathForRow:indexPath.row-1 inSection:indexPath.section];
[self.tableView scrollToRowAtIndexPath:previousIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
MUInfoMateriaTableViewCell *newCell = (MUInfoMateriaTableViewCell *)[self.tableView cellForRowAtIndexPath:previousIndexPath];
[newCell.textField becomeFirstResponder];
}
Problem is that the -cellForRowAtIndexPath: method returns
the cell only if it is visible.
That's because the cell doesn't exist when its row isn't visible. UITableView only keeps those cells it needs to draw the table. That's why you get lots of -tableView:cellForRowAtIndexPath: messages when you scroll the table -- the table is asking its data source to provide the cells that it doesn't have.
If you want to use your current approach, you'll need to scroll the table so that the row about to be edited becomes visible, as demonstrated in Gabriele Petronella's answer.

UITableView in customCell hiding one button also hide the next button of other cells

e.g as you are watching the tableView having cell with plus Button, when I press that button it hides the current button but when I scroll the tableview Some other button on other customCells are also get hide but I didn't hide them. please help me out. How to fit this problem.
The UITableView is caching the cells, which means it doesn't store all cells at all time, which is brilliant in terms of memory. The problem with it, that it has no reference to the value stored in that cell when the cell is reused. What is often done, is that all values stored in a dynamic cell is stored in separate NSMutableArrays.
For your problem, you could add an array with the boolean values indicating whether they are hidden or not, and read from that in the tableView:cellForRowAtIndexPath: delegate method with
cell.hidden = [[self.yourArray objectAtIndex:indexPath.row] boolValue];
And in the button callback method you should change the hiddenproperty as well as updating the value in the array.
i'll do it as follow:
first you have to track the button's state:
Shown
or
Hidden
this is done by holding the state in an NSMutableArray
in the viewDidLoad method add the following
NSMutableArray *shownButtons = [[NSMutableArray alloc] init];
then in your tableView:cellForRowAtIndexPath do the following
NSString *tmpIndexPathString = [NSString stringWithFormat:#"%d",indexPath.row];
if ([shownButtons containsObject:tmpIndexPathString])
{
[cell.myButton setHidden:YES];
}
else
{
[cell.myButton setHidden:NO];
}
In the tableView:cellForRowAtIndexPath: delegate method you need to loop through all the visible cells:
for (UITableViewCell *cell in [self.tableView visibleCells]) {
// now you have a cell that you can update
}
You also have to remember to update your data source so scrolling the table will update the cells accordingly and not show the plus button. If you're updating your data source what you can do is reload the cell in the above for loop for example.

iPhone dev - Having problems adding text to cells of a UITableView

I am trying to make an app that will display a bunch of different people's names and address in different cells of a UITableView. First I'm just trying to get the hand of adding text to the cells, and it's not really working. I have the following code, where myTableViewController is just a subclass of UITableViewController that loads in its view from a nib (without any drastic customizations):
myTableViewController * tvc = [[myTableViewController alloc] initWithStyle:UITableViewStylePlain];
NSIndexPath *path1 =[NSIndexPath indexPathForRow:0 inSection:0];
NSIndexPath *path2 =[NSIndexPath indexPathForRow:1 inSection:0];
[self presentModalViewController:tvc animated:YES];
UITableViewCell * cell1 = [tvc.tableView cellForRowAtIndexPath:path1];
UITableViewCell * cell2 = [tvc.tableView cellForRowAtIndexPath:path2];
cell1.textLabel.text = #"test";
cell2.textLabel.text = #"test2";
I was expecting to see a tableView animate onto the screen, followed by the first and second cells being filled with the text "test" and "test2" respectively. What I get is a blank tableview animating onto the screen. Then if I scroll down so that the top cell is hidden and scroll back up so that the top cell is again visible, the top cell will now have the text "test" in it. But the second cell down never shows any text, just the top one.
Can someone please point me in the right direction? I also would like to have the text in the tableview BEFORE it loads onto the screen, but it looks like the cells don't exist unless they are being displayed. Please help me.
Populating a table view takes the form of a data-source pattern, where the table view calls your data source object for data to put in individual cells.
Try reading through the Table View Programming Guide.
You should use the delegate and the datasource protocol methods.

Scroll to cell then get subview

I have cells in a table with custom fields that allow entry of numbers using a custom numpad. That includes a tab. When tabbing from the bottom of the table to the top, I need to (i) scroll the table to the top and (ii) select the custom field in that table.
I'm finding that if I call cellForRowAtIndexPath directly after I've called scrollToRowAtIndexPath, the former returns a null cell. If called again, after the scroll has completed, the cell is returned as expected. I need the cell intself so I can get the custom view by tag value, and select it.
I have been trying to find a solution. One might be to insert a delay after the scroll to allow it to complete. But I've tried using performSlector:withObject:afterDelay without success.
This is my code called when the tab is pressed.
-(void) customDualViewTabbed:(CustomDualView *)dualView {
UITableViewCell *cell=(UITableViewCell*)([dualView superview].superview);
NSIndexPath *fromIndexPath=[self.tableView indexPathForCell:cell];
NSIndexPath *toIndexPath;
if ((fromIndexPath.row+1)<[self.tableView numberOfRowsInSection:fromIndexPath.section]){
toIndexPath = [NSIndexPath indexPathForRow:(fromIndexPath.row+1) inSection:fromIndexPath.section];
}
else {
toIndexPath = [NSIndexPath indexPathForRow:0 inSection:fromIndexPath.section];
[[self tableView] scrollToRowAtIndexPath:toIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
//get cell we tabbed to
cell=[self.tableView cellForRowAtIndexPath:toIndexPath];
CustomDualView *dualView2 =(CustomDualView *)[cell viewWithTag:dualView.tag];
[dualView2 setSelected:(SelectedState)SelectedLeftTabbed];
}
The cells in a UITableView are only loaded as needed. As a result, if the cell that you are scrolling to resides at an index that currently off-screen, you will get a nil response from cellForRowAtIndexPath.
Now, since UITableview conforms to the UIScrollViewDelegate protocol, you can implement those delegate methods in addition to the UITableViewDelegate methods. Implementing one that you know will be called when your cell is in view, like scrollViewDidEndDecelerating:, and then making your call to cellForRowAtIndexPath might do the trick.

Refreshing the UITableView from UITableViewCell

Is there an easy way to trigger a [UITableView reloadData] from within a UITableViewCell? I am loading remote images to be displayed after the initial table display, and updating the image with self.image = newImage does not refresh the table. Resetting the cell's text value does refresh the table, but this seems sloppy.
MyTableViewCell.h
#interface MyTableViewCell : UITableViewCell {}
- (void)imageWasLoaded:(ImageData *) newImage;
MyTableViewCell.m
#implementation MyTableViewCell
- (void)imageWasLoaded:(UIImage *) newImageData {
self.image = newImage; //does not refresh table
//would like to call [self.tableView reloadData] here,
//but self.tableView does not exist.
//instead I use the below line
self.text = self.text; //does refresh table
}
#end
I did the exact thing that you're trying to do. The thing you're looking for is needsLayout. To wit (this is a notification observer on my UITableViewCell subclass):
- (void)reloadImage:(NSNotification *)notification
{
UIImage *image = [[SSImageManager sharedImageManager] getImage:[[notification userInfo] objectForKey:#"imageUrl"];
[self setImage:image];
[self setNeedsLayout];
}
This will pop in your image without having to reload the entire table, which can get very expensive.
Get a reference to the containing UITableView using the superview property. Then tell it to "reloadData":
UITableView *parentTable = (UITableView *)self.superview;
[parentTable reloadData];
It is less expensive to only reload the sections/rows that need reloading. In iPhone SDK 3.0 there are some methods for doing just that.
Hmm. So imageWasLoaded: is a delegate method called by some asynchronous image loading code? Actually it seems weird that setting self.image does not update the image. Have you tried to add your own UIImageView to the cell rather than using the image property? Might be kind of a hack, but this way the image should update right away (without you having to reload the whole table view, which is definitely not desireable).
Even better: If you are using SDK 3.0, you can use the new imageView property in UITableViewCell (I have not tried this, though):
self.imageView.image = newImage;
Actually, I needed to do exactly the same some weeks ago. However, my approach was to subclass UIImageView and do all the asynchronous image loading/updating in that class. The main reason I did it this way was that I wanted to have a generic and reusable component which can be used in different table view cells (or elsewhere).
CodeGrue is right. After days thinking why [subclassOjb.tableView reloadData] wasn't working I gave up trying to understand that and settled for the only two methods I know works and it's very easy to implement: 1- Notification Center (from tableviewcell you shouldn't use this one) 2- or use the superview to get a handle on your tableview property (this is perfect for uitableviewcells).
Note that using the superview method will work on your uitableviewcell class only, chances are if you have a custom cell you will have a uitableviewcell class which the superview is your tableviewcontroller or whateverClass you have with a uitableview delegate + datasource.
Now if you need to reload your tableview from another class, a datasource class let's say a singleton or whatever; you should use a block within your tableview class to load the data on a different thread, with an inner block to reload the table on the mainthread.
//handle tablview to show spinner (you have to lower your tableview out of the way yourself if you are calling this method)
[self.tableView setContentOffset:CGPointMake(0, -rf.frame.size.height) animated:YES];
//start spinner animation here
[rf beginRefreshing];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
// Load Data from data source class here
[myDataSourceClass fetchDataMethod];
dispatch_async(dispatch_get_main_queue(), ^{
//Reload data and stop spinner
[self.tableView reloadData];
[rf endRefreshing];
//handle tablview to hide spinner atrributed title
[self.tableView setContentOffset:CGPointMake(0, 0) animated:YES];
});
});
If you are on a different view and something happens that you need to reload your table on your table view. Well at that point your table is not on screen there is no reason why you should reload your table if no one is going to see it. Just add [self.tableview reloadDAta] to your viewWillAppear method when users go back to your tableviewcontroller view it will reload the table on the fly.
Anyway for tableviewcell just use the following inside your action to reloadData (credit to CodeGrue)
UITableView *parentTable = (UITableView *)self.superview;
[parentTable reloadData];
**different topic but for those of you starting now. This will save you from possible headaches -
after creating your tableclass and before you even start to deal with it's delegate methods - do this: in your viewDidLoad add the following code:
self.tableView.delegate = self;
self.tableView.dataSource = self;
Anybody that was looking for a way to reload the table from the cell and figured out that CodeGrue's answer does not work, you can still do it this way but you need to double check.
Disclaimer
You should always use a delegate pattern for this, what follows is a hack that might break again in future versions.
Hack to reload the data from the cell
UITableView *parentTable = (UITableView *)self.superview;
if (![parentTable isKindOfClass:[UITableView class]]) {
parentTable = (UITableView *) parentTable.superview;
}
[parentTable reloadData];
try [yourtableview reloadData];
after setting the image to a new image