I need to create an application that have the same behavior as built-in Reminder App. There are some problems to create this:
Cells that have dynamic height and "grow" as the content of the UITextView
Select the touched cell
Refresh the height of the cell when the user has edited the content(on the fly)
I've already solved the dynamic height, using some trick.
The problem that remains is:
How to know which cell the user has selected if the cell is "fully" with the UITextView ?
Right Now i've used the method textViewDidBeginEditing to know the cell and scroll the UITableView to it:
- (void)textViewDidBeginEditing:(UITextView*)textView
{
MemoViewCell* cell = (MemoViewCell *)[self parentCellFor:textView];
if (cell)
{
NSIndexPath* indexPath = [self.tableView indexPathForCell:cell];
currentIndexPath = indexPath;
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
}
- (UITableViewCell*)parentCellFor:(UIView*)view
{
if (!view)
return nil;
if ([view isMemberOfClass:[MemoViewCell class]])
return (UITableViewCell*)view;
return [self parentCellFor:view.superview];
}
How to refresh the cell height without lost the focus?
To this, i've used this method:
-(void)textViewDidChange:(UITextView *)textView
{
NSMutableDictionary *dataSourceItem = [self.model.dataSource objectAtIndex:currentIndexPath.row];
[dataSourceItem setObject:textView.text forKeyedSubscript:#"body"];
[self.model.dataSource setObject:dataSourceItem atIndexedSubscript:currentIndexPath.row];
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
When the text change, i get the new text, i change it in the Model and i call beginUpdated and endUpdates.
Sure, this works, but all is extremly slow... do you have some idea to to this in a more elegant way and maybe... efficient?
If i call reloadRowsAtIndexPaths: i lost the focus on the cell.
First of all, if you use the delegate method - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; in your dynamic height trick, remember to implement this method along - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath;, it helps a lot in terms of performance.
Then to focus on your specific project, if I correctly understood your goal, what you really want is to update the layout of your table view only when your text view is modified. Currently you're updating it at each text change. You might consider using a condition inside -(void)textViewDidChange:(UITextView *)textView, such as :
if ([textView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height !=
textView.frame.size.height) {
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
The condition given as example will work only if your existing cell layout respects the intrinsic content size of the text view, otherwise you will need to adapt the condition.
Related
Hi i have been searching around to find a way to remove a static cell with animation but i have not found a solution to the problem.
I have also tried: [tableView deleteRowsAtIndexPaths:[self.taskArray objectAtIndex:indexPath.row] withRowAnimation:UITableViewRowAnimationFade];
but no success.
You need to hide cell before it is shown, in UITableViewDelegate's tableView:willDisplayCell:forRowAtIndexPath: method. This is the last method in your control where you can manipulate the cell display. It does not remove the space the cell takes, so another thing you can try is to set cell row's height to 0 using the tableView:heightForRowAtIndexPath: method of the same protocol.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
if (yourCell) {
return 0;
} else {
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
}
As your tableview is static, you cannot use deleteRowsAtIndexPath. The best option would probably be to migrate your data into a dataSource method for the tableview.
If this is impossible, then this answer ("UITableView set to static cells. Is it possible to hide some of the cells programmatically?") gives that the best method is to use the third party library
StaticDataTableViewController 2.0 (https://github.com/xelvenone/StaticDataTableViewController).
Otherwise you have to use the somewhat hacky method of changing the row height to 0.
In my app I am trying to display Mkmapview in UITableView. When I tap the cell, it has to expand and show the map. Then if I tap another cell, it should act again expand and show gallery view , the previous cell must collapse and map should be hide.
thanks in advance,
You have to keep track of the state (or height) of the expanding/collapsing cell. Then report that height in tableView:heightForRowAtIndexPath:. Then add the code below to trigger the resizing.
[self.tableView beginUpdates];
[self.tableView endUpdates];
For example:
- (void)toggleMapCell {
_mapCellExpanded = !_mapCellExpanded;
if (_mapCellExpanded)
_mapCellHeight = 200.0;
else
_mapCellHeight = 44.0;
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath isEqualTo:_mapCellIndexPath])
return _mapCellHeight;
else
return 44.0;
}
If you have to re-layout the cell's content. I think the best place is to do it in the cell's layoutSubviews.
I have a TableView with a custom cell. One of the subviews in the cell is a UIButton. When a user clicks on the button, I want the background to change. I get all of that working. But the problem is I cannot see the change until after I scroll the affected cell off screen and then return it on screen. But I want to see the change immediately, without the onscreen offscreen bit. How might I do that?
For a bit more about my implementation:
Inside the method (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath I have the line
....
[cell.myBtn addTarget:self action:#selector(onMyBtnTapped:) forControlEvents:UIControlEventTouchUpInside];
And then inside the onMyBtnTapped method is where I effect the color change.
So perhaps what I need to do is to redraw a specific cell from the parent view controller (?).
a bit more
I have gotten as far as getting the cell using [self.tableView cellForRowAtIndexPath:indexPath];. But now that I have the cell, I don't know how to get it to redraw itself. I do this on android all the time. I am not sure how to do it on iOS.
You can reload the UITableViewCell on button click:
[self.tableView reloadRowsAtIndexPaths:#[indexPathOfGivenCell] withRowAnimation:UITableViewRowAnimationNone];
You should be able to place the following in the method.
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
You can ask to redraw cell at visible rows:
NSArray *visibleIndexPaths = [tableView indexPathsForVisibleRows];
[self.tableView reloadRowsAtIndexPaths:visibleIndexPaths withRowAnimation: UITableViewRowAnimationFade];
I have a tableView, with a UIWebView in each row.
When the webView is tapped, I'd like for it to just expand and take over the entire screen.
However, there a few problems, and I'm not sure which is interfering. First I tried something like this:
webView.frame = self.view.bounds;
webview.bounds = self.view.bounds;
cell.clipsToBounds = NO;
webView.clipsToBounds = NO;
However, the webView seems to have trouble overtaking the cell, doesn't expand to proper width and generally doesn't work. I also thought about manipulating the cell height, but it seems to be more trouble than it should be.
So basically, I'd like to move this webView to the top of the stack and change its frame size.
Otherwise I may just instantiate a new webView which seems like a waste. Any ideas? Thanks
Don't change the frame of the web view. Have the web view tied to all four sides of the cell using constraints (or at least the top and bottom), and when you tap on the cell, and it gets selected, use the indexPathForSelectedRow property in heightForRowAtIndexPath to make that cell as big as the table view.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([[tableView indexPathForSelectedRow] isEqual:indexPath]) {
return tableView.frame.size.height;
}else{
return 44;
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
I don't know why you would put a WebView in a cell.. I'm no expert but wouldn't the webview be recreated everytime you scroll?
You could use SVWebViewController and place the following code in your didselectrowatindexpath to present it modally.
SVModalWebViewController *webViewController = [[SVModalWebViewController alloc] initWithAddress:#"http://google.com"];
[self presentViewController:webViewController animated:YES completion:NULL];
I have a UITextField in a custom cell inside table. I created new class DataCell which is subclass of UITableViewCell. Inside DataCell I created outlets for textfields and I also have method inside implementation file which uses 'editing did end' and I manipulate textField values there.
I am now wondering how to get rowIndex or number of the cell, as each time I click + button new custom cell is loaded on the table. If I get tag I always get same tag number regardless of the cell I selected.
The text field passed to your delegate is a subview of the cell's contentView.
UITableViewCell *cell = (UITableViewCell*) textField.superview.superview;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
You can use this logic when you are not sure of hierarchy between textfield and cell.
UITableViewCell *cell = nil;
UIView *parentView = textField.superview;
while(parentView) {
if([parentView isKindOfClass:[UITableViewCell class]]) {
cell = parentView;
break;
}
parentView = parentView.superview;
}
if(cell)
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
Add tags to the text field in the tableView:cellForRowAtIndexPath: method. In this example, I have a custom cell with a label and a text field:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RDCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.label1.text = self.theData[indexPath.row];
cell.textField.tag = indexPath.row;
return cell;
}
It sounds like you are maybe handling the end of editing in your custom cell class, but you might want to consider doing it in the table view controller instead, since that gives you easy access to the model, which I presume you are modifying with what the user types in the text field. If you do that, then you should connect the text field's delegate property up to the table view controller in IB.
If we're accepting fragile answers then for the sake of contributing something new to the conversation:
CGRect rectInTableView =
[tableView convertRect:textField.bounds fromView:textField];
NSUInteger indexOfCellContainingTextField =
(NSUInteger)(rectInTableView.y / tableView.rowHeight);
Assumptions that make it fragile: (i) all rows are the same height; (ii) the height is set on the table view. If you haven't implemented UITableViewDelegate -tableView:heightForRowAtIndexPath: then both of those assumptions will hold true. You're also taking advantage of the fact that casting a positive float to an integer rounds down.
I would argue that although still not completely clear of assumptions, this is less fragile than Mundi's solution because it makes assumptions only about things you do directly control (ie, cell sizing) and not about things you don't (ie, the view hierarchy UIKit uses internally to present table views).