How to get a UITableViewCell from one of its subviews - ios

I have a UITableView with a UITextField in each of the UITableViewCells. I have a method in my ViewController which handles the "Did End On Exit" event for the text field of each cell and what I want to be able to do is update my model data with the new text.
What I currently have is:
- (IBAction)itemFinishedEditing:(id)sender {
[sender resignFirstResponder];
UITextField *field = sender;
UITableViewCell *cell = (UITableViewCell *) field.superview.superview.superview;
NSIndexPath *indexPath = [_tableView indexPathForCell:cell];
_list.items[indexPath.row] = field.text;
}
Of course doing field.superview.superview.superview works but it just seems so hacky. Is there a more elegant way? If I set the tag of the UITextField to the indexPath.row of the cell its in in cellForRowAtIndexPath will that tag always be correct even after inserting and deleting rows?
For those paying close attention you might think that I have one .superview too many in there, and for iOS6, you'd be right. However, in iOS7 there's an extra view (NDA prevents me form elaborating) in the hierarchy between the cell's content view and the cell itself. This precisely illustrates why doing the superview thing is a bit hacky, as it depends on knowing how UITableViewCell is implemented, and can break with updates to the OS.

Since your goal is really to get the index path for the text field, you could do this:
- (IBAction)itemFinishedEditing:(UITextField *)field {
[field resignFirstResponder];
CGPoint pointInTable = [field convertPoint:field.bounds.origin toView:_tableView];
NSIndexPath *indexPath = [_tableView indexPathForRowAtPoint:pointInTable];
_list.items[indexPath.row] = field.text;
}

One slightly better way of doing it is to iterate up through the view hierarchy, checking for each superview if it's an UITableViewCell using the class method. That way you are not constrained by the number of superviews between your UITextField and the cell.
Something along the lines of:
UIView *view = field;
while (view && ![view isKindOfClass:[UITableViewCell class]]){
view = view.superview;
}

You can attach the UITableViewCell itself as a weak association to the UITextField, then pluck it out in the UITextFieldDelegate method.
const char kTableViewCellAssociatedObjectKey;
In your UITableViewCell subclass:
- (void)awakeFromNib {
[super awakeFromNib];
objc_setAssociatedObject(textField, &kTableViewCellAssociatedObjectKey, OBJC_ASSOCIATION_ASSIGN);
}
In your UITextFieldDelegate method:
UITableViewCell *cell = objc_getAssociatedObject(textField, &kTableViewCellAssociatedObjectKey);
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
//...
I'd also recommend re-associating every time a cell is dequeued from the UITableView to ensure that the text field is associated with the correct cell.

Basically in this case, I would prefer you to put the IBAction method into cell instead of view controller. And then when an action is triggered, a cell send a delegate to a view controller instance.
Here is an example:
#protocol MyCellDelegate;
#interface MyCell : UITableViewCell
#property (nonatomic, weak) id<MyCellDelegate> delegate;
#end
#protocol MyCellDelegate <NSObject>
- (void)tableViewCell:(MyCell *)cell textFieldDidFinishEditingWithText:(NSString *)text;
#end
In a implementation of a cell:
- (IBAction)itemFinishedEditing:(UITextField *)sender
{
// You may check respondToSelector first
[self.delegate tableViewCell:self textFieldDidFinishEditingWithText:sender.text];
}
So now a cell will pass itself and the text via the delegate method.
Suppose a view controller has set the delegate of a cell to self. Now a view controller will implement a delegate method.
In the implementation of your view controller:
- (void)tableViewCell:(MyCell *)cell textFieldDidFinishEditingWithText:(NSString *)text
{
NSIndexPath *indexPath = [_tableView indexPathForCell:cell];
_list.items[indexPath.row] = text;
}
This approach will also work no matter how Apple will change a view hierarchy of a table view cell.

Related

How to shift the control on the next UITextField when return key is press

I have a UITableViewController with custom UITableViewCell. Where each cell contains a UITextField. Now what i want is to shift the control from one text field to the every next text field when return key is pressed.
what i m thinging to do is this...
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
NSIndexPath *currentIndexPath=[self.tableView indexPathForCell:(UITableViewCell*)textField.superview.superview];
if(currentIndexPath.row<_excerciseData.totalBlanks-1){
NSIndexPath nextIndexPath=???
UITextField *nextTextFeild=[(UITableViewCell*)[self.tableView cellForRowAtIndexPath:nextIndexPath] viewWithTag:2];
[nextTextFeild becomeFirstResponder];
}else{
[textField resignFirstResponder];
}
return YES;
}
but for this i don't know, how to find the indexPath of very next UITableViewCell. Can anybody help me to find this? this... :)
Since you already have a custom table view cell the simplest solution would be to add a property that holds the index path. Then in the delegate method where you return the cell simply assign the current index path to the cell which may later be used.
This will not work very well for the last cell in the section or the table view itself. What you should do is report the event to the table view owner (the view controller) from the cell itself which may then compute what the next cell index is: If you have multiple sections it must check if it should go to a new section and if it is the last cell simply do nothing (or go to the beginning of the table view if you wish).
For forwarding messages you may try one of the procedures mentioned in the answer here: IOS: Navigate through tableView and show table view data on next view
Just to be clear, the text field delegate should be the cell itself!
Better you can do like this,
CGPoint pointInTable = [field convertPoint:field.bounds.origin toView:_tableView];
NSIndexPath *indexPath = [_tableView indexPathForRowAtPoint:pointInTable];
Reference : How to get a UITableViewCell from one of its subviews
or do like this,
id view = [textfield superview];
while (view && [view isKindOfClass:[UITableViewCell class]] == NO) {
view = [view superview];
}
UITableViewCell *tableViewCell = (UITableViewCell *)view;
Reference : How to get UITableView from UITableViewCell?
Better way you can use tag property of textField. Do like this.
While you creating cell with cellForRowAtIndexPath
give a tag value for textField inside the cell like:
cell.yourTextField.tag = indexPath.row + someConstant; // someConstant is just to make sure that there is no textfield with the same tag value
and in your textField delegate: - (BOOL)textFieldShouldReturn:(UITextField *)textField implement the following.
// Jumping with the keyboard Next button implementation.
if (UIReturnKeyNext == textField.returnKeyType) //may be u can avoid this check
{
id nextField = [self viewWithTag:textField.tag + 1];
if ([nextField isKindOfClass:[UITextField class]])
{
[nextField becomeFirstResponder];
}
}
Hopes the textFieldDelegates are implemented in the tableview's owner class.
Solution here
#prgma mark - UITableViewDelegate Methods
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *stringCell = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
cell.yourTextField.tag = indexPath.row;
return cell;
}
#prgma mark - UITextFieldDelegate Methods
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
NSUInteger index = textField.tag;
UITextField *nextTextField = (UITextField*)[self.view viewWithTag:index+1];
[nextTextField becomeFirstResponder];
return YES;
}

If a UITableViewCell has multiple buttons, where is the best place to handle touch events

I'm presenting a lot of data in format of a table with multiple columns. Almost each column has a button (up to 4 in total) and each row is a UITableViewCell.
How could I detect that the buttons were touched and where should I handle touch events of the buttons? I'm certain, it shouldn't be a didSelectRowAtIndexPath method though.
As soon as, I could detect which button was pressed, I would fetch the data in that particular row and manipulate it. So, I need to know the indexPath of the row, as well as what button was pressed on it.
You can subclass UIButton with two properties row and column and implement the logic below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"
forIndexPath:indexPath];
MyButton *button1 = (MyButton *)[cell viewWithTag:1];
button1.row = indexPath.row;
button1.column = 1; // view tag
[button1 addTarget:self
action:#selector(clickAction:)
forControlEvents:UIControlEventTouchUpInside];
// button2,button3...
return cell;
}
-(void)clickAction:(MyButton *)sender {
// now you can known which button
NSLog(#"%ld %ld", (long)sender.row, (long)sender.column);
}
Generalized undetailed answer:
Create UITableviewcell subclass, link cell ui elements to this class.
Add method configureWithModel:(Model*)model; //Model being the information you want the cell to represent
Manipulate that information or
If you need to manipulate the screen or other objects. You need to give the table view cell subclass a reference to the other objects when the cell is created. (in code or in storyboard or in nib).
how to handle button presses in ios 7: Button in UITableViewCell not responding under ios 7 (set table cell selection to none)
how to link a button: http://oleb.net/blog/2011/06/creating-outlets-and-actions-via-drag-and-drop-in-xcode-4/
If those four views are UIButton then you will receive the tap events on each button or if they are not UIButton then you should add UITapGestureReconiser on each of this views
Several options here. But I would do the following:
Adopt a Delegate Protocol in your custom cell class (see here: How to declare events and delegates in Objective-C?) . This will handle the target selector for the buttons. Pass this message back to your view controller with the sender. To detect which cell it was in do the following:
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
CGRect senderFrame = CGRectMake(buttonPosition.x, buttonPosition.y, sender.frame.size.width, sender.frame.size.height);
From here you can decide what the do. Use the buttons .x coordinate to determine which button it was or specify a different tag for each button in cellForRowAtIndexPath:. Or if you want to grab the index path of the cell you can do:
NSArray *indexPaths = [YOUR_TABLE_VIEW indexPathsForRowsInRect:senderFrame];
NSIndexPath *currentIndexPath = [indexPaths lastObject];
Because each button has a different action, the only thing you need to get at runtime is the indexPath of the button. That can be done by looking at the button's superviews until a cell is found.
- (IBAction)action1:(UIButton *)button
{
UITableViewCell *cell = [self cellContainingView:button];
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
MyDataModel *object = self.objects[indexPath.row];
// perform action1 on the data model object
// Now that the data model behind indexPath.row was done, reload the cell
[self.tableView reloadRowsAtIndexPaths:#[indexPath]
withRowAnimation: UITableViewRowAnimationAutomatic];
}
- (id)cellContainingView:(UIView *)view
{
if (view == nil)
return nil;
if ([view isKindOfClass:[UITableViewCell class]])
return view;
return [self cellContainingView:view.superview];
}
There: no delegates, no tags, and the action doesn't care about the internals of the cell.
You will still want to subclass UITableViewCell with the four buttons (call them button1, button2, button3, and button4 if you don't have better names). You can make all the connection is Interface Builder. This will only be needed for populating object data into the cell during -tableView:cellForRowAtIndexPath:.
Ideally, you should create a custom cell by subclassing UITableViewCell and implement the actions for each of these buttons in that cell. If your view controller needs to know about these actions, you can define a protocol, MyCustomCellDelegate or similar, and have your view controller conformed to that protocol. Then MyCustomCell will be able to send messages to the view controller when user interacts with its buttons or other controls.
As in the example code below, you can create a cell in storyboard or nib and hook one of the button's action to firstButtonAction method of CustomTableCell class.
Also, you need to set your view controller as delegate property of CustomTableCell object created and implement the method buttonActionAtIndex: of CustomTableCellDelegate in your view controller class. Use controlIndexInCell param passed to this method to determine which button might have generated the action.
#protocol CustomTableCellDelegate <NSObject>
- (void) buttonActionAtIndex:(NSInteger)controlIndexInCell
#end
In CustomTableCell.h class
#interface CustomTableCell: UITableViewCell
#property (nonatomic, weak) id <CustomTableCellDelegate> delegate
- (IBAction) firstButtonAction:(id)sender
#end
In CustomTableCell.m class
#implementation CustomTableCell
#synthesize delegate
- (IBAction) firstButtonAction:(id)sender{
if ([delegate respondToSelector:#selector(buttonActionAtIndex:)])
[delegate buttonActionAtIndex:0];
}
#end
This is a personal preference on how I like to handle situations like these, but I would first subclass UITableViewCell because your table cells do not look like a default iOS UITableViewCell. Basically you have a custom set up, so you need a custom class.
From there you should set up your 4 IBActions in your header file
- (IBAction)touchFirstButton;
- (IBAction)touchSecondButton;
- (IBAction)touchThirdButton;
- (IBAction)touchFourthButton;
You do not need to pass a sender in these actions, because you will not be using that object in these methods. They are being created to forward the call.
After that set up a protocol for your UITableViewSubClass
#protocol UITableViewSubClassDelegate;
Remember to put that outside and before the #interface declaration
Give your sell a delegate property
#property (nonatomic, strong) id<UITableViewSubClassDelegate> delegate;
and finally define your actual protocol, you will need to set up 4 methods, 1 for each button and take your subclass as a parameter
#protocol UITableViewSubClassDelegate <NSObject>
- (void)forwardedFirstButtonWithCell:(UITableViewSubClass*)cell;
- (void)forwardedSecondButtonWithCell:(UITableViewSubClass*)cell;
- (void)forwardedThirdButtonWithCell:(UITableViewSubClass*)cell;
- (void)forwardedFourthButtonWithCell:(UITableViewSubClass*)cell;
#end
This will be placed outside of the #interface #end section at the bottom
After that create a configureWithModel: method in your #interface and #implementation as well as a property for your model
#interface:
#property (nonatomic, strong) Model *model;
- (void)configureWithModal:(Model*)model;
#implementation:
- (void)configureWithModal:(Model*)model {
self.model = model;
// custom UI set up
}
From here you should configure your action methods in your #implementation file to call the delegate methods, i'm only showing the first one, but you would do this with all of the IBActions
- (void)configureWithModal:(Model*)model {
[self.delegate forwardFirstButtonWithCell:self];
}
From here your custom cell set up is done and we need to go back to the UIViewController that is displaying the UITableView. First go into the header file of the view controller, and import your custom UITableViewCellSubClass and then setup the class to implement this protocol.
It should look something like this
#interface MYViewController : UIViewController <UITableViewSubClassDelegate>
from there you should go into your cellForRowAtIndexPath: method and configure your custom UITableViewCell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCellSubClass *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
cell.delegate = self;
Model *cellModel = self.tableData[indexPath.row];
[cell configureWithModel:cellModel];
return cell;
}
Now go into your cell class and copy paste all of the protocol methods into your viewController class. I will display one as an example.
In your UIViewController:
- (void)forwardedFirstButtonWithCell:(UITableViewSubClass*)cell {
Model *cellModel = cell.model;
// do stuff with model from outside of the cell
}
do that for all methods and you should be good.
Remember to have all your #imports in so there's no forward declarations and remember to link up the IBActions to your storyboard or xib files. If you want a custom xib for your table cell you will have to check if the cell is nil and then allocate a new one, but if you are using prototype cells then this should be sufficient.
For simplicity sakes i put forwardFirstButtonWithCell: but i would encourage making the name something that describes what it's doing such as, displayPopOverToEnterData or something similar. From there you could even change the parameters of the delegate protocol methods to take models instead so instead of
- (void) displayPopOverToEnterDataWithCell:(UITableViewSubClass*)cell;
make it
- (void) displayPopOverToEnterDataWithModel:(Model*)model;
but, i don't know what type of information you need to access from the cell. So update these methods as you see fit.

iOS How to select row, when a person selects the UITextField inside it?

So I have a UITableView, where all cells have a UITextField in them as a subview with a tag=1. What's troubling me is that I want when a user clicks on a textField and edits it to know on which row has that happened. What I think can solve it, is to make the cell select itself when the subview (UITextField) is selected. How can I achieve that?
I tried with an array, but because cells are reused, it wouldn't work. Looping through all of the rows would be simply too slow.
Disable the UITextField in each of your cells by default and use your UITableView delegate method didSelectRowAtIndexPath: to
Store the indexPath of the selected row in a property
Enable the UITextField
Make the UITextField first responder
Define the property in your class extension:
#interface MyTableViewController ()
#property (strong, nonatomic) NSIndexPath *activeIndex;
#end
In your implementation of didSelectRowAtIndexPath::
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.activeIndex = indexPath;
AddCell *selectedCell = (AddCell *)[self.tableView cellForRowAtIndexPath:indexPath];
[selectedCell.textField setEnabled:YES];
[selectedCell.textField becomeFirstResponder];
}
You'll want to disable the UITextField again when it resigns its first responder status.
Assuming your UITableViewController is the delegate for each UITextField, you can do this in your implementation of the UITextFieldDelegate method:
-(void)textFieldDidEndEditing:(UITextField *)textField
{
[textField setEnabled:NO];
}
textfield.superview.superview gives you the cell instance.
Use the delegate to get the action
The correct approach is to convert the textFields bounds so it is relative to the tableView, and then use the origin of this rect to get the indexPath.
CGRect rect = [self.tableView convertRect:textField.bounds fromView:textField];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:rect.origin];
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionMiddle];
Use the UITextFieldDelegate to know when the user start editing a UITextField (with textFieldDidBeginEditing:).
Two solutions then:
Solution 1: Subclass your cell and make it the delegate of the UITextField.
Then in textFieldDidBeginEditing: of your custom cell, make the cell selected:
// MyCustomCell.m
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[self setSelected:YES animated:YES];
}
Solution 2: Make the view controller the UITextField delegate, and select the right cell from there.
// MyTableViewController.m
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
// Find the cell containing our UITextField
UIView *cell = textField.superview;
while (![cell isKindOfClass:[UITableViewCell class]])
{
cell = cell.superview;
}
// Make the cell selected
[(UITableViewCell *)cell setSelected:YES animated:YES];
}
I'd recommend the first solution, as Andrey Chevozerov said in the comments of one of the answers:
It's better to not use superview for such tasks. Especially in cascade.
Code below will return NSIndexPath.This can be written in UITextField delegate -
[tableView indexPathForRowAtPoint:textField.superview.superview.frame.origin];
Try above line your code.
Why you're using tags for textfields?
The proper way is:
create custom class for cell;
make an outlet for your UITextField;
when creating cells assign your view controller as a delegate for the cell's text field;
assign a tag == indexPath.row to the corresponding cell's text field;
in textFieldShouldBeginEditing place a code for selecting cell:
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:textfield.tag inSection:0] animated:YES scrollPosition:UITableViewScrollPositionNone];

Select UITableViewCell text when a new cell is added to a UITableView

I have a basic UITableView which contains basic UITableViewCells. When I add a new cell to the table I'd like to:
1) scroll to the new row
2) select all of the text in the new UITableViewCell so that the keyboard becomes visible and the user can immediately edit the cell's text.
The next time through the event loop (after calling -reloadData on the table view) I do:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:wordIndex inSection:0];
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:(UITableViewScrollPositionNone)];
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
[cell.textLabel becomeFirstResponder];
cell.highlighted = YES;
The scrolling is correct but the text in the cell is not selected.
A UILabel can't become a first responder because it returns NO from canBecomeFirstResponder. To give the illusion of a label that is edited, you could try using a UITextField with a borderStyle of UITextBorderStyleNone.
Be careful with the timing of the becomeFirstResponder call, since a control can't be first responder if it is not a subview of a window. This can happen if you are scrolling to a row that is very far off screen and immediately trying to call becomeFirstResponder before it is added as a visible row.
As in previous answer you can create custom child of UITableViewCell, add UITextField on that.
#interface MyTableViewCell : UITableViewCell
#property(nonatomic, retain) IBOutlet UITextField *editableTextField;
#end
Than you can catch didSelectRowAtIndexPath in your controller and set it's textfield to become first responder
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
[cell.editableTextField becomeFirstResponder];
}
UPD: as a trouble resolve for Not-in-window-cell trouble, described by upper answer, you must call selectCellAtIndexPath in method
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView

Is there any way refresh cell's height without reload/reloadRow?

I make a view like imessage, just input text into the bottom text view. I use table view to do this, and the text view in the last cell. when I input long text that more than one line, I need the text view and the cell become tailer. so I need refresh cell's height. but if I use table view's reload or reload row, the content in text view will disappear and the keyboard will disappear too. Is there any way better to fix it?
May be I should use tool bar to do it easy? but I still doubt table view can do it.
The cells will resize smoothly when you call beginUpdates and endUpdates. After those calls the tableView will send tableView:heightForRowAtIndexPath: for all the cells in the table, when the tableView got all the heights for all the cells it will animate the resizing.
And you can update cells without reloading them by setting the properties of the cell directly. There is no need to involve the tableView and tableView:cellForRowAtIndexPath:
To resize the cells you would use code similar to this
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
NSString *newText = [textView.text stringByReplacingCharactersInRange:range withString:text];
CGSize size = // calculate size of new text
if ((NSInteger)size.height != (NSInteger)[self tableView:nil heightForRowAtIndexPath:nil]) {
// if new size is different to old size resize cells.
// since beginUpdate/endUpdates calls tableView:heightForRowAtIndexPath: for all cells in the table this should only be done when really necessary.
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
return YES;
}
to change the content of a cell without reloading I use something like this:
- (void)configureCell:(FancyCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
MyFancyObject *object = ...
cell.textView.text = object.text;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FancyCell *cell = (FancyCell *)[tableView dequeueReusableCellWithIdentifier:#"CellWithTextView"];
[self configureCell:cell forRowAtIndexPath:indexPath];
return cell;
}
// whenever you want to change the cell content use something like this:
NSIndexPath *indexPath = ...
FancyCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
[self configureCell:cell forRowAtIndexPath:indexPath];
I've written a subclass of UITableViewCell to handle this functionality.
.h file:
#import <UIKit/UIKit.h>
#protocol AECELLSizeableDelegate;
#interface AECELLSizeable : UITableViewCell
#property (weak, nonatomic) id <AECELLSizeableDelegate> delegate;
#property IBOutlet UIView *viewMinimized;
#property IBOutlet UIView *viewMaximized;
#property BOOL maximized;
#property CGFloat height;
- (IBAction)clickedConfirm:(id)sender;
- (IBAction)clickedCancel:(id)sender;
- (void)minimizeForTableview: (UITableView*)tableView;
- (void)maximizeForTableview: (UITableView*)tableView;
- (void)toggleForTableview: (UITableView*)tableView;
#end
#protocol AECELLSizeableDelegate <NSObject>
- (void)sizeableCellConfirmedForCell: (AECELLSizeable*)cell;
- (void)sizeableCellCancelledForCell: (AECELLSizeable*)cell;
#end
.m file:
#import "AECELLSizeable.h"
#implementation AECELLSizeable
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)minimizeForTableview: (UITableView*)tableView
{
self.maximized = NO;
[self.viewMinimized setHidden:NO];
[self.viewMaximized setHidden:YES];
self.height = self.viewMinimized.frame.size.height;
[tableView beginUpdates];
[tableView endUpdates];
}
- (void)maximizeForTableview: (UITableView*)tableView
{
self.maximized = YES;
[self.viewMinimized setHidden:YES];
[self.viewMaximized setHidden:NO];
self.height = self.viewMaximized.frame.size.height;
[tableView beginUpdates];
[tableView endUpdates];
}
- (void)toggleForTableview:(UITableView *)tableView
{
if (self.maximized) {
[self minimizeForTableview:tableView];
} else {
[self maximizeForTableview:tableView];
}
}
- (void)clickedConfirm:(id)sender
{
[self.delegate sizeableCellConfirmedForCell:self];
}
- (void)clickedCancel:(id)sender
{
[self.delegate sizeableCellCancelledForCell:self];
}
#end
Example Usage:
Create a UITableViewController with a static UITableView in IB
Add a cell to the tableview that is a AECELLSizeable (or subclass of it)
Create two UIViews in this cell. One UIView will be used for the content visible while minimized, the other will be used for the content visible while maximized. Ensure these cells start at 0 on the y axis and that their height is equal to that of the height you wish to have the cell for each state.
Add any subviews to these two views you desired. Optionally add confirm and cancel UIButtons to the maximized UIView and hook up the provided IBActions to receive delegate callbacks on these events.
Set your tableview controller to conform to the AECELLSizeableDelegate and set the cell's delegate property to the tableview controller.
Create an IBOutlet in your UIViewController's interface file for the AECELLSizeable cell.
Back in IB, ensure the cell's initial height is that of the minimized version and connect the IBOutlet you just previously created.
Define the tableview's heightForRowAtIndexPath callback method in the tableview controller's implementation file as such:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
//Sizeable Cell
return self.cellSizeable.height;
} else {
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
}
In your tableview controller's viewDidLoad method, call minimizeForTableview on your cell to ensure it starts off minimized.
Then, call maximizeForTableview: on the cell when it is selected via the didSelectRowAtIndexPath callback from the tableview (or however else you would like to handle it) and call the minimizeForTableview: method on the cell when you receive the canceled / confirmed delegate callbacks from the cell (or, again, however else you'd like to handle it).
Check out this library. This is an implementation of message bubbles using UITableView. Keep in mind that every time a cell is displayed, cellForRow:atIndexPath is called and the cell is drawed.
EDIT
You can use heightForRowAtIndexPath
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
Messages *message = [self.messageList objectAtIndex:[indexPath row]];
CGSize stringSize = [message.text sizeWithFont:[UIFont fontWithName:#"HelveticaNeue-Light" size:13]
constrainedToSize:CGSizeMake(320, 9999) lineBreakMode:UILineBreakModeWordWrap];
return stringSize.height + 78;
}
Table view cells won't smoothly resize. Dot.
However, since your text view is in the last cell, you are lucky because you can easily simulate a row resizing. Having a text view in the middle of the table would be much more difficult.
Here is how I would do it: put your text view on top of the table view. Sync its position with the contentOffset of the tableView in scrollViewDidScroll:. And use the animatable contentInset of the tableView in order to leave room for the textView, as the user types in it. You may have to make sure your textView is not scrollable.

Resources