添加/删除 UITableView 行时如何防止文本字段变空?

Posted

技术标签:

【中文标题】添加/删除 UITableView 行时如何防止文本字段变空?【英文标题】:How to prevent textfields from getting empty when adding/removing UITableView rows? 【发布时间】:2013-05-25 17:17:33 【问题描述】:

我正在创建一个 UITableView,可以在其中添加产品信息。在每一行中,用户可以添加有关产品的信息,显然,用户可以自己设置行数。

用户可以通过点击导航栏中的“添加行”或“删除行”按钮一次添加或删除一行。这就是它的工作原理:

- (void)viewDidLoad

    [super viewDidLoad];

    tableRows = [NSNumber numberWithInt:12];



-(void) addRow

    NSNumber *addRow =[NSNumber numberWithInt:1];
    tableRows= [NSNumber numberWithInt:(tableRows.intValue + addRow.intValue)];
    [self.tableView reloadData];

    NSLog(@"%@", tableRows);


-(void) removeRow

    NSNumber *addRow =[NSNumber numberWithInt:1];
    tableRows= [NSNumber numberWithInt:(tableRows.intValue - addRow.intValue)];
    [self.tableView reloadData];

    NSLog(@"%@", tableRows);

- (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 tableRows.intValue;


    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

        static NSString *CustomCellIdentifier = @"CustomCellIdentifier ";
        CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier: CustomCellIdentifier];
        if (cell == nil) 
            NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomCell"
                                                         owner:self options:nil];
            for (id oneObject in nib) if ([oneObject isKindOfClass:[CustomCell class]])
                cell = (CustomCell *)oneObject;
            cell.selectionStyle = UITableViewCellSelectionStyleNone;

        
        NSUInteger *row = [indexPath row];

        return cell;
    

编辑工作完美,但是当我添加或删除一行时,我在表格视图的文本字段中插入的文本消失了。

有人知道如何预防吗?

【问题讨论】:

【参考方案1】:

有几件事:表格视图没有责任记住每个单元格中的内容。它在滚动时丢弃单元格,并要求数据源通过 cellForRowAtIndexPath 再次初始化它们。 Reloaddata - 您在添加/删除方法中使用 - 将导致表格刷新所有可见单元格。不要期望任何未在 cellForRowAtIndexPath 中设置的内容出现在您的表格中。

接下来,该表的“模型”是一个 NSNumber“tableRows”,表示行数。对于表格视图来说,这是一个不充分的模型。将其替换为 NSMutableArray。至少,这个数组应该包含代表每个文本字段状态的字符串。 (它可能需要更精细的对象,但从字符串开始)。

这样,您的视图控制器类将看起来更像这样......

// this is your table's model
@property (nonatomic, strong) NSMutableArray *rows;

// in init for the class
_rows = [NSMutableArray array];

// somewhere else, put some data in it
[self.rows addObject:@"Foo"];
[self.rows addObject:@"Bar"];

现在你的数据源方法:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
    // Return the number of rows in the section.
    return self.rows.count;

然后,在 cellForRowAtIndexPath 中:

NSUInteger *row = [indexPath row];   // as you have it
NSString *rowText = self.rows[row];  // this is new syntax, the old way is [self.rows objectAtIndex:row];

// your CustomCell needs to provide a way to get at the textField it contains
// it might do this with an outlet or viewWithTag...
cell.myTextField.text = rowText;
return cell;

最后,单元格中的文本字段带来了特殊的挑战。当视图不滚动时如何保存它们的当前状态。这个问题已经在 SO (here, for example) 中被多次询问和回答。简而言之,最常见的解决方案是让视图控制器成为单元格中文本字段的代表。然后,在 textFieldDidEndEditing 上,像这样在模型中保存 textField 的值...

- (void)textFieldDidEndEditing:(UITextField *)textField 

    NSIndexPath *indexPath = [self indexPathOfCellWithSubview:textField];
    self.rows[indexPath.row] = textField.text;


// I think this is the best way to get the index path of a cell, given some subview it contains
- (NSIndexPath *)indexPathOfCellWithSubview:(UIView *)view 

    while (view && ![view isKindOfClass:[UITableViewCell self]]) 
        view = view.superview;
    
    return [self.tableView indexPathForCell:(UITableViewCell *)view];

EDIT 说模型不仅仅是一个字符串。这是您将应用 NSObject 的自定义子类的地方。

// MyModel.h
@interface MyModel : NSObject

@property (strong, nonatomic) NSString *itemName;
@property (assign, nonatomic) CGFloat price;
@property (strong, nonatomic) NSString *imageFileName;
@property (strong, nonatomic) UIImage *image;

- (id)initWithItemName:(NSString *)itemName price:(CGFloat)price imageFileName:(NSString *)imageFileName;

- (NSString *)stringPrice;
- (void)setStringPrice:(NSString *)stringPrice;

@end

// MyModel.m
@implementation MyModel

- (id)initWithItemName:(NSString *)itemName price:(CGFloat)price imageFileName:(NSString *)imageFileName 

    self = [self init];
    if (self) 
        _itemName = itemName;
        _price = price;
        _imageFileName = imageFileName;
    
    return self;


// override the image getter to "lazily" create and cache the image
// if the images are on the web, this will require a slighly more elaborate method
// employing NSURLConnection.
- (UIImage *)image 

    if (!_image) 
        _image = [UIImage imageNamed:self.imageFileName];
    
    return _image;


// added these to show you how you can conveniently encapsulate other
// behavior, like type conversion or validation, though, real ones of these
// would probably use NSNumberFormatter
- (NSString *)stringPrice 

    return [NSString stringWithFormat: @"%.2f", self.price];


- (void)setStringPrice:(NSString *)stringPrice 

    self.price = [stringPrice floatValue];

现在您可以像这样创建一个并将其添加到您的表格中。 (一定要#import "MyModel.h"

[self.rows addObject:[[MyModel alloc] initWithItemName:@"Toaster" price:39.95 imageFileName:@"toaster.png"]];

包含表的视图控制器或多或少保持不变(当您对一个类进行大量更改而对密切相关的类更改很少时,它会告诉您您的 OO 设计可能非常好)。对于替换字符串的花哨模型,我们需要更改 cellForRowAtIndexPath...

NSUInteger *row = [indexPath row];
MyModel *myModel = self.rows[row];

cell.itemNameTextField.text = myModel.itemName;
cell.priceTextField.text = [myModel stringPrice];
cell.imageView.image = myModel.image;

// additional OO idea: teach your cell how to configure itself and move the foregoing code there
// [cell configureWithModel:myModel];

return cell;

另一个编辑:我们可以教这个模型如何将自己发布到远程 Web 服务,如下所示:

- (void)post 
    NSString *hostStr = @"http://myhost/create_product.php";
    NSURL *url = [NSURL URLWithString:hostStr];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";

    NSString *post =[NSString stringWithFormat:@"item_name=%@&price=%@",self.itemName, [self stringPrice];
    NSString *postEscaped = [post stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSData *postData = [postEscaped dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];

    [request setHTTPBody:postData];
    [request setValue:@"application/x-www-form-urlencoded charset=utf-8" forHTTPHeaderField:@"Content-Type"];

    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) 

                           if (!error) 
                               NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                               NSLog(@"response %@", string);
                            else 
                               NSLog(@"error %@", error);
                           
                       ];

在 .h 中声明此方法,在帖子中添加您认为合适的其他字段(例如图像文件名等)

在您的视图控制器中,选择表示用户想要提交新行的操作(可能是在文本字段完成编辑时?),然后添加...

// text field finished editing
MyModel *myModel = self.rows[indexPath.row];
myModel.itemName = textField.text;
[myModel post];

由于图像可能来自您的远程服务,您需要更改我之前添加的延迟加载图像获取器。加载此图像的正确方法是异步,但这样做会使与表视图的交互变得复杂,无法在此讨论。请参阅苹果文档或this SO post 了解更多信息。同时,这是同步获取图像的快速(但基本上是错误的)方法...

   - (UIImage *)image 

    if (!_image) 
        // note - now the file name must be of the form @"http://host/path/filename.png"
        NSURL *url = [NSURL URLWithString:self.imageFileName
        _image = [UIImage imageWithContentsOfURL:url];
    
    return _image;

【讨论】:

谢谢!我正试图让它现在工作。一个问题:我的 tableViewCells 包含多个文本字段。每个单元格包含 2 个文本字段(产品名称和价格)、一个文本视图(用于制作简短的产品描述)和一个 UIImage。因此,我可能不得不制作一个二维可变数组,对吧?如果是这样,我如何编辑您的代码以使其与二维数组一起使用? 当然,现在将进行编辑。您的情况需要 NSObject 的自定义子类,而不是数组中的任何其他维度。 哇!这是一些非常令人印象深刻的代码,至少对我这个初学者来说是这样。我现在正在应用它来测试它。起初我在想一个二维数组,因为所有输入的产品,都会通过 php 发送到一个 mysql 数据库。稍后我会尝试弄清楚它是如何工作的,但非常感谢! 你知道是否可以分别访问每个值以将其发送到 mysql 吗?如果没有,我可能无法使用它。现在它有效,所以这是我现在需要做的关于你的答案的最后一件事;) 您能解释一下“分别访问每个值”是什么意思吗?现在我们有了一个很好的封装模型对象,我们可以教它如何将自己发布到远程 Web 服务。你是这么想的吗?【参考方案2】:

查看您的 cellForRowAtIndexPath 代码会很有帮助,我们需要更多地了解您打算在其中存储数据的模型。

当您从表格中删除一行时,该单元格将被丢弃,并且表格视图不会自动记住内容。您必须在发生更改时将更改保存在模型对象中,然后在从 cellForRowAtIndexPath 返回单元格时使用它来填充单元格的内容。

【讨论】:

我正在使用 customCells,我将很快发布我的 cellForRowAtIndexpath。我不想记住已删除单元格的内容。假设我有 2 行,我只在第一行添加文本。当我按“添加行”时,我的文字不见了。当我按“删除行”时也会发生同样的情况。 我贴出来了!希望你能用这个做点什么

以上是关于添加/删除 UITableView 行时如何防止文本字段变空?的主要内容,如果未能解决你的问题,请参考以下文章

从 UITableView 插入和删除行时如何正确更新数据源?

为啥删除 UITableView 行时会出现错误?

尝试从 UITableView 中删除行时断言失败

当用户点击加号按钮插入行时 UITableView 响应

使用 CoreData 删除行时 UITableView 崩溃

删除 UITableView 行时删除通知(Swift)