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

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.

Related

How do I create UITableViewCells with dynamic height that are compatible with both iOS7 and iOS8?

I am creating an app that I want to run under both iOS7 and iOS8, that has variable table cell heights. In iOS7 I used to define a prototype cell and then use this in heightForRowAtIndexPath to calculate the height, based on the size of the text placed into labels.
This no longer seems to work when using Xcode 6. I created a small test app today to see if I was going crazy (maybe I am). I started by NOT defining heightForRowAtIndexPath at all. When I test it with iOS7 and iOS8 devices it behaves differently. In the iOS8 device it automatically determines the cell height and works fine. In iOS7 the cells are returned all the same height. It doesn't do the automatic height adjustment for the cells with variable text, for iOS7, only for iOS8.
So... me thinking... hmmm... under iOS8 I can use auto layout to determine the height and only implement heightForRowAtIndexPath for iOS7...
Okay, so... I tried adding in heightForRowAtIndexPath. Under iOS7 it crashes on method systemLayoutSizeFittingSize. I played with this for hours and nothing I do seems to change this. It is extremely simple code.
Here is the UITableViewController code:
//
// TestTableViewController.m
// testTables
//
#import "TestTableViewController.h"
#import "Cell1.h"
#interface TestTableViewController ()
#property (nonatomic, strong) Cell1 *cellPrototype;
#end
#implementation TestTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"Cell1" bundle:nil] forCellReuseIdentifier:#"Cell1"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 10;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
self.cellPrototype = [self.tableView dequeueReusableCellWithIdentifier:#"Cell1"];
self.cellPrototype.label1.text = [self cellContents:indexPath];
[self.cellPrototype layoutIfNeeded];
CGSize size = [self.cellPrototype systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Cell1 *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell1" forIndexPath:indexPath];
cell.label1.text = [self cellContents:indexPath];
return cell;
}
- (NSString*)cellContents:(NSIndexPath *)indexPath {
if (indexPath.row == 0) {
return #"one line";
}
else if (indexPath.row == 1) {
return #"two lines I think will result from this piece of text that goes over";
}
else if (indexPath.row == 2) {
return #"three lines I think will result from this piece of text that goes over and then even extends a little more again";
}
else {
return #"three lines I think will result from this piece of text that goes over and then even extends a little more again, and then extends over more and more lines like it will go on forever and never end but in fact it does end right here.";
}
}
#end
Here is the cell header (there is nothing in the .m file):
//
// Cell1.h
// testTables
//
#import <UIKit/UIKit.h>
#interface Cell1 : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *label1;
#end
Here is the xib for the cell. As you can see, it's not complex:
So... does this old method not work any more? How can I get variable height cells that will run in both iOSes?
Many of the warnings you are describing are explained in detail here. Automatic Preferred Max Layout Width is not available on iOS versions prior to 8.0
Prior to iOS 8, the preferredMaxLayoutWidth needed to be set manually. This property can be set anywhere if it's unlikely to change but my preferred solution is to use a subclassed UILabel that automatically sets the preferredMaxLayoutWidth.
Your subclassed UILabel would override layoutSubviews so that its preferredMaxLayoutWidth is always the same as its width.
- (void)layoutSubviews {
self.preferredMaxLayoutWidth = self.bounds.size.width;
[super layoutSubviews];
}

How to set up this custom UITableViewCell

I have taken over an iOS project and have to refactor a list of views into a UITableView. I am using Storyboards and have subclassed UITableViewCell. One subclass is called MenuItemCell and has a headerLabel, detailLabel, and priceLabel which are properties set up in the Storyboard and configured in MenuItemCell. I am able to manipulate these via cellForAtIndexPath like this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *MenuItemCellIdentifier=#"MenuItemCell";
id dic=self.tmpMenu.listItems[indexPath.row];
if([dic isKindOfClass:[MenuItem class]]){
MenuItemCell *cell = [self.menuTV dequeueReusableCellWithIdentifier:MenuItemCellIdentifier];
MenuItem *menuItem=(MenuItem *)dic;
cell.menuItem=menuItem;
cell.headerLabel.text=menuItem.header;
cell.headerLabel.numberOfLines=0;
cell.priceLabel.text=menuItem.price;
// how to handle this custom spotView
if([menuItem hasInstoreImage]){
UIView *instoreImageDot=[self circleWithColor:[UIColor redColor] radius:4];
[cell.spotView addSubview:instoreImageDot]; // ON SCROLLING, this populates to all the different table cells
}
return cell;
}
return nil;
}
The last piece is that there is a custom UIView called spotView. Currently, I am creating this circle in code in my controller via circleWithColor and trying to add to [cell.spotView] but scrolling causes this to populate on different table cells. How should I set this up? I have added a method to my custom view but this suffers from the same problem.
Cells get reused, you will need to tell the tableView to remove the custom View
if([menuItem hasInstoreImage]){
UIView *instoreImageDot=[self circleWithColor:[UIColor redColor] radius:4];
[cell.spotView addSubview:instoreImageDot];
}else{
//remove it if condition is not met
//or You can add a place holder view instead
}
What is happening is that iOS is reusing cells as you scroll and some of the reused cells already have the instoreImageDot view added as a subview.
You really shouldn't do layout stuff in the cellForRowAtIndexPath method. It should only ever be used to dequeue a reusable cell and then set the data for the cell. All the layout stuff should be handled by the cell itself.
Don't create the instoreImageDot in the controller. Add a method in your custom cell - something like (written in C#, but should be easy to translate):
UpdateCell(MenuItem item, bool hasInstoreIamge)
{
menuItem = item;
headerLabel.text = item.header;
priceLabel.text = item.price;
headerLabel.numberOfLines=0;
if (hasInstoreImage)
{
// code to add the instoreImageDot as a subview of the cell
}
}
Also in the Custom Cell, Implement the prepareForReuse method and inside this method, remove the instoreImageDot view from the cell - so that it can only ever be added once.
- (void)prepareForReuse {
if([self.subviews containsObject:instoreImageDot])
{
[instoreImageDot removeFromSuperview];
}
[super prepareForReuse];
}
Now your cellForRowAtIndexPath method can look like:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *MenuItemCellIdentifier=#"MenuItemCell";
id dic=self.tmpMenu.listItems[indexPath.row];
if([dic isKindOfClass:[MenuItem class]]){
MenuItemCell *cell = [self.menuTV dequeueReusableCellWithIdentifier:MenuItemCellIdentifier];
MenuItem *menuItem=(MenuItem *)dic;
cell.UpdateCell(menuItem, [menuItem hasInstoreImage]);
return cell;
}
return nil;
}

UITextField inside UITableViewCell - method for preventing text reset

I have a UITableView tall enough that it necessitates scrolling. The top-most cell in the table contains a UITextField for the user to enter some text.
The standard way to build this might be to create and add the text field and add it to a cell created or recycled in cellFOrRowAtIndexPath: However, this constant re-creation means that the text entered in the field is erased when the cell is scrolled out and back into view.
The solutions I've found so far suggest using UITextField delegation to track the text as it changes and store it in an iVar or property. I would like to know why this is recommended instead of the simpler approach I am using:
I am creating the UITextField in the init method of the UITableViewController and immediately storing it in a property. In cellFOrROwAtIndexPath I am simply adding the pre-existing field instead of initializing a new one. The cell itself can be recycled without issue, but because I am always using the one and only UITextField, the content is maintained.
Is this a reasonable approach? What might go wrong? Any improvements (perhaps I could still create the field in cellForRowAtIndexPath but first check if the property is nil?)
When you are creating cells in cellForRowAtIndexPath you have to use one reusable identifier for that first cell (ie. cellId1) and another for the rest (ie. cellId2).
If you do this, when you get the cell for the first element by calling [tableView dequeueReusableCellWithIdentifier:#"cellId1"] you will always get the same Object and will not be reused by other cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = nil;
// Only for first row
if (indexPath.row == 0) {
static NSString *cellId1 = #"cellId1";
cell = [tableView dequeueReusableCellWithIdentifier:cellId1];
if (cell == nil) {
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId1];
}
}
else {
static NSString *cellId2 = #"cellId2";
cell = [tableView cellId2];
if (cell == nil) {
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault cellId2];
}
}
// do whatever
return cell;
}
If there is only one UITextField, then I agree that your approach would be better/same as compared to using UITextField delegation (I think).
However, let us assume that you want to "expand" your view so that there are about 7-8 or more TextFields now. Then if you go about using your approach, then the problem will be that you will be storing 7-8 or more TextFields in memory and maintaining them.
In such a situation, a better approach would be that you create only that number of textfields as visible on screen. Then you create a dictionary which would maintain the content present in the textfield (which you can get by UITextFieldDelegate methods). This way, the same textfield can be used when the cell is reused. Only the values will change and will be dictated by the values in the dictionary.
On a sidenote, do minimal creation in cellForRowAtIndexPath as that is called during every table scroll and so creating a textField in cellForRowAtIndexPath can be expensive.
#import "ViewController.h"
#import "TxtFieldCell.h"
#define NUMBER_OF_ROWS 26
#interface ViewController ()<UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>
#property (weak, nonatomic) IBOutlet UITableView *tablView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tablView.datasource = self; //set textfield delegate in storyboard
textFieldValuesArray = [[NSMutableArray alloc] init];
for(int i=0; i<NUMBER_OF_ROWS; i++){
[textFieldValuesArray addObject:#""];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - TableView Datasource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TxtFieldCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TxtFieldCellId" forIndexPath:indexPath];
cell.txtField.tag = indexPath.row;
if (textFieldValuesArray.count > 0) {
NSString *strText = [textFieldValuesArray objectAtIndex:indexPath.row];
cell.txtField.text = strText;
}
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return NUMBER_OF_ROWS;
}
#pragma mark - TextField Delegate
- (void)textFieldDidEndEditing:(UITextField *)textField {
[textFieldValuesArray replaceObjectAtIndex:textField.tag withObject:textField.text];
}

How to get a UITableViewCell from one of its subviews

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.

Why can't I initialize a UITextField from a subclass of UITableViewCell?

I've created a subclass of UITableViewCell for an iPad app. I need to dynamically generate text fields, take input from the user, and then store that information in an array. I thought of asking the UITableViewCell for the UITextField.text object, which would hold whatever the user wrote before my View Controller's segue (I'm saving the NSString objects upon the segue being called). So I've got an array of UITableViewCells which I ask for the UITextField.text object. But for some reason while my UITableViewCell subclass is being created, my UITextField is not. I can call UITableViewSubclass and it's initialized, but UITableViewSubclass.UITextField is nil.
Here's my UITableViewCell Subclass header (Yes, the UITextField is connected in the storyboard):
#import <UIKit/UIKit.h>
#interface ConditionCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UITextField *condition;
#end
Here's my implementation file:
#import "ConditionCell.h"
#implementation ConditionCell
#synthesize condition;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
self.condition = (UITextField *)[self viewWithTag:10];
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
This here is the Table View Controller handling the table that contains the cells:
.h file:
#import <UIKit/UIKit.h>
#import "ConditionCell.h"
#interface ConditionsTableViewController : UITableViewController
#property (strong, nonatomic) NSMutableArray *conditionCellArray;
- (void)addNewConditionCell;
#end
.m file:
#import "ConditionsTableViewController.h"
#interface ConditionsTableViewController ()
#end
#implementation ConditionsTableViewController
#synthesize conditionCellArray = _conditionCellArray;
- (NSMutableArray *)conditionCellArray
{
if (_conditionCellArray == nil) {
// Create the array object
_conditionCellArray = [[NSMutableArray alloc] init];
}
return _conditionCellArray;
}
- (void)addNewConditionCell
{
ConditionCell *condCell = [[ConditionCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"conditionCell"];
[self.conditionCellArray addObject:condCell];
[self.tableView beginUpdates];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.conditionCellArray.count-1 inSection:0];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
[self.tableView endUpdates];
[self.tableView reloadData];
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.conditionCellArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"conditionCell";
ConditionCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[ConditionCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
//cell.condition = (UITextField *)[cell viewWithTag:1];
return cell;
}
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
/*
<#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:#"<#Nib name#>" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
*/
}
#end
This Table View Controller lives inside a UIView Controller as the table view does not take up the whole screen. When the user presses an 'ok' button there is a segue that is triggered and it is here that I ask this Table View Controller for the array containing the UITableViewCells, which I then run through a foreach to get their .text properties. Unfortunately I can't seem to get anything I input into the text fields, hence the .text's are always nil. If anyone could help me with this issue it would be greatly appreciated!
You might find this much easier to do using the free Sensible TableView framework. The framework has these text field cells out of the box, and can even create them automatically from your array.
I figured out a better way to do what I wanted to do here that works. Turns out that the way iOS's UITableView works is totally different from what I wanted to do. UITableView works by looking at your storyboard and given the identifiers for the cells, it creates them and allows you to set their properties within the cellForRowAtIndexPath method. However, when the cell goes offscreen, it is not retained as it's own separate object; it is reused. So, you can think of it as if when you scroll a table view, the cells that disappear to one end reappear on the other end with new information. This is key - UITableView want YOU to provide the cell's information. It was not made for input of information directly on a UITableViewCell, which is what I wanted to do.
So what I ended up doing was copy-pasting my cells into their own .xib file, and in the subclass initWithStyle:reuseIdentifier method, do:
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"ConditionCell" owner:self options:nil];
self = [nibArray objectAtIndex:0];
And that creates the cell with whatever style - setup - UI elements you want.
Next, I want to hold on to a reference to the cell, because that cell has a textbox, and I need to save what's on the textbox when the user presses a "done" button. However, testing revealed the reuse problem I explained above. So how to do this? In my Table's view controller, whenever the user wants to add a new textbox (and presses the button to do so) I have a method which does
[self.conditionCellArray insertObject:[[ConditionCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"conditionCell"] atIndex:0];
This adds a new cell to an array - this is important because I need to have a reference to ALL cells at all times. (It is adding the cell at index 0 because I want to insert it at the top). Then, in the cellForRowAtIndexPath method, I did
return [self.conditionCellArray objectAtIndex:indexPath.row];
Which will return the corresponding cell. Bear in mind, from what I have read this whole thing about keeping a reference to each and every cell in the table is contrary to Apple's stated best practices when using UITableView. However, as I said before, UITableView is meant to display information, not to gather it from user input. So this is why I had to break the rules, if you will, to achieve the desired effect (that I wanted). I hope this helps others who are looking to do the same thing; and if there is a better way don't be shy about telling me.
EDIT: Oh by the way, when you copy paste the cells created in storyboard to their own .xib file make sure to disconnect any IBOutlets and change their class back to UITableViewCell. That way there won't be any problems or conflicts when you connect your .xib file cell.

Resources