简体   繁体   中英

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM