iOS之訪問自定義cell的textField.text的N種方法

iOS之訪問自定義cell的textField.text的N種方法

前言

問題背景:

自定義cell中有一個UITextField型別的子控制元件。我們經常要在tableView中拿到某個cell內textField的文字內容進行一些操作。比如某些app的註冊介面就是以tableView的形式存在的,註冊時往往需要註冊姓名、暱稱、郵箱、地址、聯絡方式等資訊。然後點選註冊或者提交,這些資訊就會被提交到遠端伺服器。那麼我們怎麼在tableView中準確的拿到每一行cell中textField的text呢?以下我將要分四個方法分別介紹並逐一介紹他們的優缺點,大家可以在開發中根據實際情況有選擇的採用不同的方法。如下圖,就是我之前開發的一個app中用xib描述的一個cell,當使用者點選“註冊”或者“提交”button時候,我需要在控制器中拿到諸如“法人姓名”這一類的資訊:

四個方法告訴你如何在tableView中拿到每一個cell中的textField.text

  • 四個方法分別如下:

    • 通過控制器的textField屬性來拿到每一個cell內textField.text

    • 通過系統預設傳送的通知來拿到每一個cell內textField.text

    • 通過自定義的通知來拿到每一個cell內textField.text

    • 通過block來拿到每一個cell內textField.text


方法一

  • 1 .cell的.h檔案宣告一個IBOutlet的屬性,使其和xib描述的cell中的textField進行關聯。

  • 2 .在tableViewController.m的類擴充套件中宣告為每一個cell的textField都宣告一個UITextField型別的屬性,一一對應。

  • 3 .在cellForRowAtIndexPath:資料來源方法中給控制器的每個UITextField型別屬性賦值為cell.textField。

TableViewCell.h檔案中的contentTextField引用xib中的textField:

#import <UIKit/UIKit.h>
@interface TableViewCell : UITableViewCell
/**
*  cell的標題
*/
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
/**
*  cell的文字框
*/
@property (weak, nonatomic) IBOutlet UITextField *contentTextField;
@end

控制器中宣告UITextField型別的屬性。

@interface YQBInfoViewController ()
/**
*  標題
*/
@property(nonatomic, strong) NSArray *titles;
/**
*  佔位文字
*/
@property(nonatomic, strong) NSArray *placeHolders;
/**
*  姓名
*/
@property(nonatomic, weak) UITextField *nameTextField;
/**
*  年齡
*/
@property(nonatomic, weak) UITextField *ageTextField;
/**
*  地址
*/
@property(nonatomic, weak) UITextField *addressTextField;
@end

資料來源方法cellForRowAtIndexPath:中給控制器的UITextField型別屬性賦值。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 在這裡把每個cell的textField 賦值給 事先宣告好的UITextField型別的屬性
// 以後直接操作控制器的這些屬性就可以拿到每個textField的值
switch (indexPath.row) {
case 0:
// 姓名
self.nameTextField = cell.contentTextField;
break;
case 1:
// 年齡
self.ageTextField = cell.contentTextField;
break;
case 2:
// 地址
self.addressTextField = cell.contentTextField;
break;
default:
break;
}
return cell;
}

以下是方法一的demo地址:https://github.com


方法二

我們知道UITextField內容改變時會傳送通知。與UITextField相關的通知有三個,如下:

UIKIT_EXTERN NSString *const UITextFieldTextDidBeginEditingNotification;
UIKIT_EXTERN NSString *const UITextFieldTextDidEndEditingNotification;
UIKIT_EXTERN NSString *const UITextFieldTextDidChangeNotification;
  • 1.我們只需要讓tableview控制器註冊
-UITextFieldTextDidChangeNotification/UITextFieldTextDidEndEditingNotification 通知。
  • 2.在資料來源方法cellForRowAtIndexPath:中對cell.textField.tag賦值為indexPath.row。這樣就可以區分每一行的textField。

  • 3.然後在監聽到通知後呼叫的方法中,根據textField.tag拿到textField的內容。

但是,問題來了,`如果tableView是grouped樣式的呢?這樣就有可能存在兩個textField具有相同的tag!所以,以上提供的思路只適用於plained樣式的tableView。grouped樣式的tableView建議用下面的方法。

解決方法:

  • 自定義textField,給textField新增NSIndexPath型別的屬性indexPath。我們這次給textField的indexPath賦值而不是tag。這樣就可以在監聽到通知後呼叫的方法中,根據indexPath來區分不同的section和row。

自定義UITextField

#import <UIKit/UIKit.h>
@interface CustomTextField : UITextField
/**
*  indexPath屬性用於區分不同行cell
*/
@property (strong, nonatomic) NSIndexPath *indexPath;
@end

注意:

  • 如果你自定義的cell是用xib描述的,不要忘記給cell的textField指定型別為你自定義的textField,此例中我自定義的是CustomTextField,如下圖:

控制器註冊通知

// 註冊通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contentTextFieldDidEndEditing:) name:UITextFieldTextDidEndEditingNotification object:nil];

給自定義的textField的indexPath屬性賦值

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
AliyunSalesUnifiedEditCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
// 如果不止一個section,那麼傳遞indexPath.row有可能衝突
// cell.contentTextField.tag = indexPath.row;
// 所以傳遞indexPath,相當於把section也傳遞給contentTextField
cell.contentTextField.indexPath = indexPath;
return cell;
}

監聽到通知後呼叫的方法

// 在這個方法中,我們就可以通過自定義textField的indexPath屬性區分不同行的cell,然後拿到textField.text
- (void)contentTextFieldDidEndEditing:(NSNotification *)noti
{
CustomTextField *textField = noti.object;
if (textField.indexPath.section == 0) {
switch (textField.indexPath.row) {
case 0: // 名稱
self.name = textField.text;
break;
case 1: // 年齡
self.age = textField.text;
break;
case 2: // 地址
self.address = textField.text;
break;
default:
break;
}
} else if (textField.indexPath.section == 1) {
// 同上,請自行腦補
} else if (textField.indexPath.section == 2) {
// 同上,請自行腦補
} else {
// 同上,請自行腦補
}
}

以下是方法二的demo地址:https://github.com/textFieldCell2


方法三

其實方法三和方法二很像,都需要給自定義的textField新增indexPath屬性,也需要傳送通知,然後在通知中心對這個通知註冊監聽。區別在於,方法二傳送的是系統自帶的通知

UITextFieldTextDidEndEditingNotification

而方法三將要傳送自定義通知。

  • 1>給CustomTextField新增indexPath屬性。

  • 2>給自定義cell新增CustomTextField型別contentTextField屬性。

  • 3>cell遵守UITextFieldDelegate協議,成為textField屬性的delegate。

  • 4>cell實現協議方法-textFieldDidEndEditing:(UITextField *)textField

  • 5>textFieldDidEndEditing:協議方法中傳送一個自定義的通知,並且把textField.text通過userInfo字典發出去。

具體實現程式碼:

給CustomTextField新增indexPath屬性

#import <UIKit/UIKit.h>
@interface CustomTextField : UITextField
/**
*  indexPath屬性用於區分不同的cell
*/
@property (strong, nonatomic) NSIndexPath *indexPath;
@end

給自定義cell新增CustomTextField型別contentTextField屬性

#import <UIKit/UIKit.h>
@class CustomTextField;
@interface TableViewCell : UITableViewCell
/**
*  cell的標題
*/
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
/**
*  cell的文字框
*/
@property (weak, nonatomic) IBOutlet CustomTextField *contentTextField;
@end

遵守協議,設定delegate,實現協議方法

#import "TableViewCell.h"
#import "CustomTextField.h"
@interface TableViewCell ()<UITextFieldDelegate>
@end
@implementation TableViewCell
- (void)awakeFromNib {
[super awakeFromNib];
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.contentTextField.delegate = self;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 使contentTextField聚焦變成第一響應者
[self.contentTextField becomeFirstResponder];
}
#pragma mark - UITextFieldDelegate
- (void)textFieldDidEndEditing:(UITextField *)textField
{
NSDictionary *userInfo = @{
@"textFieldText":self.contentTextField.text
};
[[NSNotificationCenter defaultCenter] postNotificationName:@"CustomTextFieldDidEndEditingNotification" object:self.contentTextField userInfo:userInfo];
}
  • 6>控制器註冊並監聽該通知

  • 7>在監聽到通知的方法中通過userInfo拿到textField的text屬性

  • 8>- (void)viewWillDisappear:(BOOL)animated方法中移除監聽

  • 9>完畢

註冊通知

// 如果不能保證控制器的dealloc方法肯定會被呼叫,不要在viewDidLoad方法中註冊通知。
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// 注意:此處監聽的通知是:UITextFieldTextDidEndEditingNotification,textField結束編輯傳送的通知,textField結束編輯時才會傳送這個通知。
// 想實時監聽textField的內容的變化,你也可以註冊這個通知:UITextFieldTextDidChangeNotification,textField值改變就會傳送的通知。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(aLiYunTextFieldDidEndEditing:) name:@"CustomTextFieldDidEndEditingNotification" object:nil];
//    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contentTextFieldDidEndEditing:) name:UITextFieldTextDidEndEditingNotification object:nil];
}

移除通知

- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// 在這個方法裡移除通知,因為:
// 防止控制器被強引用導致-dealloc方法沒有呼叫
// 其他介面也有textField,其他介面的textField也會傳送同樣的通知,導致頻繁的呼叫監聽到通知的方法,而這些通知是這個介面不需要的,所以在檢視將要消失的時候移除通知 同樣,在檢視將要顯示的時候註冊通知
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"CustomTextFieldDidEndEditingNotification" object:nil];
}

接收到通知回撥方法

// 接收到註冊監聽的通知後呼叫
- (void)aLiYunTextFieldDidEndEditing:(NSNotification *)noti
{
CustomTextField *textField = noti.object;
if (!textField.indexPath) {
return;
}
NSString *userInfoValue = [noti.userInfo objectForKey:@"textFieldText"];
NSLog(@"text:%@,userInfoValue:%@",textField.text,userInfoValue);
if (textField.indexPath.section == 0) {
switch (textField.indexPath.row) {
case 0: // 名稱
self.name = textField.text;
break;
case 1: // 年齡
self.age = textField.text;
break;
case 2: // 地址
self.address = textField.text;
break;
default:
break;
}
} else if (textField.indexPath.section == 1) {
// 同上,請自行腦補
} else if (textField.indexPath.section == 2) {
// 同上,請自行腦補
} else {
// 同上,請自行腦補
}
}

以下是方法三的demo地址:https://github.com/textFieldCell3

方法三相對於方法二的好處在於:方法三傳送的是自定義通知,而方法二傳送的是系統自帶的通知。

因為專案開發中,受專案複雜度影響,難免會出現不同的控制器介面都會有UITextField型別(或者其子型別)的物件而沒有釋放,當textField開始編輯、內容發生改變、結束編輯時,都會傳送相同的通知。此時如果我們採用監聽系統自帶的通知的方法,就有可能監聽到我們不需要的改變從而影響了業務資料。

舉個例子:A和B控制器都是UITableViewController型別的物件,A、B控制器介面上都有 UITextField 型別(或者其子型別)的子控制元件。並且A、B控制器都註冊了系統自帶的UITextField的通知UITextFieldTextDidChangeNotification ,且監聽到通知後都會呼叫各自的 contentTextFieldTextDidChange:方法。當A控制器pushB控制器後,我們在B控制器介面上的TextField編輯內容,A控制器此時也監聽了該通知,所以,A控制器上的contentTextFieldTextDidChange:方法也會被呼叫。這是我們不想得到的,所以,採用自定義通知的方法可以避免這一問題。

當然,我們也可以在viewWillAppear:方法中註冊通知,然後在viewWillDisAppear:方法中移除通知,這樣同樣可以避免這一為題。
另外,值得提醒的是,如果我們不能保證控制器被pop時肯定會呼叫dealloc方法,那麼建議在控制器的viewWillDisAppear:方法中移除通知,而非dealloc方法中移除。否則,使用者反覆push、pop控制器時,控制器可能會註冊多份相同的通知。


方法四

  • 1>給cell新增一個block屬性,該block屬性帶有一個NSString *型別的引數。

  • 2>給cell的textField新增target,觸發方法的事件是UIControlEventEditingChanged

  • 3>textField觸發的方法中呼叫cell的這個block屬性,並把contentTextField.text作為block的引數傳進去

  • 4>資料來源方法cellForRowAtIndexPath:中對cell的block屬性賦值(也就是拿到cell.contentTextField.text)

給cell新增一個block屬性

#import <UIKit/UIKit.h>
@interface TableViewCell : UITableViewCell
/**
*  block 引數為textField.text
*/
@property (copy, nonatomic) void(^block)(NSString *);
/**
*  cell的標題
*/
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
/**
*  cell的文字框
*/
@property (weak, nonatomic) IBOutlet UITextField *contentTextField;
@end

給textField addTarget

在事件觸發方法中呼叫block並傳遞引數

#import "TableViewCell.h"
@interface TableViewCell ()
@end
@implementation TableViewCell
- (void)awakeFromNib {
[super awakeFromNib];
self.selectionStyle = UITableViewCellSelectionStyleNone;
[self.contentTextField addTarget:self action:@selector(textfieldTextDidChange:) forControlEvents:UIControlEventEditingChanged];
// 注意:不是 UIControlEventValueChanged
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.contentTextField becomeFirstResponder];
}
#pragma mark - private method
- (void)textfieldTextDidChange:(UITextField *)textField
{
self.block(self.contentTextField.text);
}
@end

在cellforRowAtIndexPath:方法中為每個cell的block賦值

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
TableViewCell *customCell = (TableViewCell *)cell;
customCell.titleLabel.text = self.titles[indexPath.row];
customCell.contentTextField.placeholder = self.placeHolders[indexPath.row];
if (indexPath.section == 0) {
switch (indexPath.row) {
case 0:
{
customCell.block = ^(NSString *text){
self.name = text;
};
break;
}
case 1:
{
customCell.block = ^(NSString *text){
self.age = text;
};
break;
}
case 2:
{
customCell.block = ^(NSString *text){
self.address = text;
};
break;
}
default:
break;
}
} else if (indexPath.section == 1) {
// 同上,請自行腦補
} else {
// 同上,請自行腦補
}
}

以下是方法四的demo地址:https://github.com/textFieldCell4

方法四相對於方法二和方法三的好處在於:方法四沒有采用通知的方式來獲取contentTextField.text,而是採用靈活的block。並且方法四也無需自定義textField。


方法五

  • 方法五和方法四很像,只不過方法五采用了delegate方式,更好的做到了解耦。

    • 0>和方法二、方法三一樣,cell的textField屬性都需要使用自定義型別,因為我們需要給textField繫結indexPath屬性。
      1>給cell制定一份協議,協議中有一個方法,帶有兩個引數,一個是textField的text,另一個是indexPath。同時給cell新增一個delegate屬性。

    • 2>給cell的textField新增target,觸發方法的事件是UIControlEventEditingChanged

    • 3>textField觸發的方法中呼叫cell的協議方法,並把contentTextField.indexPath作為協議方法的引數傳進去

    • 4>資料來源方法cellForRowAtIndexPath:中對cell的indexPath賦值為當前的indexPath。對cell的delegate賦值為當前controller

    • 5>控制器實現cell的協議方法,在協議方法裡可以拿到textField的文字。

    • 6>在tableView:willDisplayCell:forRowAtIndexPath:方法內重新整理tableView。

#import <UIKit/UIKit.h>
@class CustomTextField;
@protocol CustomCellCellDelegate <NSObject>
@required
// cell 的contentTextField的文字發生改變時呼叫
- (void)contentDidChanged:(NSString *)text forIndexPath:(NSIndexPath *)indexPath;
@end
@interface TableViewCell : UITableViewCell
/**
*  cell的標題
*/
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
/**
*  cell的文字框
*/
@property (weak, nonatomic) IBOutlet CustomTextField *contentTextField;
/**
*  delegate
*/
@property (weak, nonatomic) id<CustomCellCellDelegate> delegate;

cell.m檔案

- (void)awakeFromNib {
[super awakeFromNib];
self.selectionStyle = UITableViewCellSelectionStyleNone;
[self.contentTextField addTarget:self action:@selector(contentDidChanged:) forControlEvents:UIControlEventEditingChanged];
}
- (void)contentDidChanged:(id)sender {
// 呼叫代理方法,告訴代理,哪一行的文字發生了改變
if (self.delegate && [self.delegate respondsToSelector:@selector(contentDidChanged:forIndexPath:)]) {
[self.delegate contentDidChanged:self.contentTextField.text forIndexPath:self.contentTextField.indexPath];
}
}

controller.m

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
cell.contentTextField.indexPath = indexPath;
cell.delegate = self;
return cell;
}
// cell的代理方法中拿到text進行儲存
- (void)contentDidChanged:(NSString *)text forIndexPath:(NSIndexPath *)indexPath {
[self.contents replaceObjectAtIndex:indexPath.row withObject:text];
}
// 更新UI
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
TableViewCell *customCell = (TableViewCell *)cell;
customCell.titleLabel.text = self.titles[indexPath.row];
customCell.contentTextField.placeholder = self.placeHolders[indexPath.row];
customCell.contentTextField.text = self.contents[indexPath.row];
}

以下是方法五的demo地址:https://github.com/TextFieldCell5

文/VV木公子(簡書作者)
原文連結:http://www.jianshu.com
著作權歸作者所有