本系列博客是本人的開(kāi)發(fā)筆記。歡迎一起討論
大家在日常開(kāi)發(fā)中應(yīng)該知道蛔添,表單提交頁(yè)面是比較難處理的痰催,一是因?yàn)閁I上需要處理UITextField/UITextView的樣式以及鍵盤的顯示隱藏;二是因?yàn)樵谔幚斫涌诘臅r(shí)候一般是POST方式需要做各種校驗(yàn)迎瞧;三是如果提交失敗夸溶,還要做各種后續(xù)諸如彈框等的處理工作。下面筆者要給大家展示的就是一個(gè)我們?nèi)粘i_(kāi)發(fā)中可能遇到的信息填寫頁(yè)面凶硅,這次我們要實(shí)現(xiàn)的效果類似于iMessage中的用戶信息編輯頁(yè)面:
可以看到缝裁,當(dāng)我們點(diǎn)擊位于屏幕下方的TextField時(shí),彈出鍵盤足绅,但隨之TableView也往上滑動(dòng)了一下捷绑,這樣就避免了鍵盤遮擋輸入框的問(wèn)題。根據(jù)上一篇文章的知識(shí)點(diǎn)氢妈,實(shí)現(xiàn)的方式相信大家應(yīng)該很容易就能想到粹污,無(wú)非是監(jiān)聽(tīng)到鍵盤彈出時(shí)更改TableView的ContentOffset值。這里首量,筆者做了個(gè)簡(jiǎn)單的Demo壮吩,實(shí)現(xiàn)的效果如下:
代碼應(yīng)該也很好理解:
-(void)viewDidLoad{
[super viewDidLoad];
//監(jiān)聽(tīng)鍵盤彈出通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardWillShowWithNotification:) name:UIKeyboardWillChangeFrameNotification object:nil];
}
//在Cell中加入U(xiǎn)ITextField,并設(shè)置Delegate為Self
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = @"cellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
UITextField *field = [[UITextField alloc] initWithFrame:CGRectMake(15, 10, SCREEN_WIDTH - 30, 24)];
field.delegate = self;
field.placeholder = [NSString stringWithFormat:@"You can input Something At Index %li",(long)indexPath.row];
[cell.contentView addSubview:field];
}
return cell;
}
//在即將編輯之前計(jì)算出TextField的位置加缘,并轉(zhuǎn)化成TableView中的位置
- (void)textFieldDidBeginEditing:(UITextField *)textField {
self.activedTextFieldRect = [textField convertRect:textField.frame toView:self.tableview];
}
//當(dāng)鍵盤彈出時(shí)鸭叙,動(dòng)態(tài)改變TableView的ContentOffset的值
- (void)keyBoardWillShowWithNotification:(NSNotification *)notification {
//get FrameEnd
CGRect rect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
//get AnimationDuration
double duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
//keyboard height > textView height, need scroll
if ((self.activedTextFieldRect.origin.y + self.activedTextFieldRect.size.height) > ([UIScreen mainScreen].bounds.size.height - rect.size.height))
{
[UIView animateWithDuration:duration animations:^{
self.tableview.contentOffset = CGPointMake(0, 64 + self.activedTextFieldRect.origin.y + self.activedTextFieldRect.size.height - ([UIScreen mainScreen].bounds.size.height - rect.size.height));
}];
}
}
可以看到,這么處理的一個(gè)繁瑣的地方是生百,必須要在TableViewCell中獲取到TextField递雀,并將TextField的Delegate設(shè)置為當(dāng)前的控制器。這顯然不是一個(gè)好的解決方案,那有沒(méi)有更好的解決方案呢蚀浆,答案是肯定的缀程,只是大家沒(méi)想到會(huì)有多么簡(jiǎn)單:
在工程的Podfile中添加如下這句話:
pod 'IQKeyboardManager'
即可搜吧。是的,引入IQKeyboardManager庫(kù)即可杨凑。他會(huì)自動(dòng)幫我們解決如上問(wèn)題滤奈,下面我們看一下刪除我們剛剛的代碼,并加入庫(kù)IQKeyboardManager后的效果:
可以看到我們的鍵盤上面多了一個(gè)小的工具欄撩满,可以選擇上下箭頭來(lái)切換需要填寫的TextField蜒程,我們的PlaceHolder也顯示在了這個(gè)小工具欄上。鑒于有些讀者可能對(duì)這個(gè)庫(kù)的使用不是很熟悉伺帘,這里對(duì)其使用做個(gè)簡(jiǎn)單的介紹:
IQKeyboardManager的開(kāi)啟/關(guān)閉
[IQKeyboardManager sharedManager].enable = YES;
默認(rèn)情況下IQKeyboardManager是開(kāi)啟的昭躺,如果我們需要針對(duì)某個(gè)控制器關(guān)閉,可以做這樣處理:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[IQKeyboardManager sharedManager].enable = NO;
}
-(void)viewWillDisappear:(BOOL)animated {
[IQKeyboardManager sharedManager].enable = YES;
[super viewWillDisappear:animated];
}
或者在 AppDelegate中注冊(cè)方法:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[IQKeyboardManager sharedManager] disableInViewControllerClass:[XXXViewController class]];
}
IQKeyboardManager工具條的顯示/隱藏
[IQKeyboardManager sharedManager].enableAutoToolbar = NO;
設(shè)置點(diǎn)擊背景收回鍵盤
[IQKeyboardManager sharedManager].shouldResignOnTouchOutside = YES;
下面我們來(lái)研究一下這個(gè)庫(kù)的源代碼伪嫁。有過(guò)一定開(kāi)發(fā)經(jīng)驗(yàn)的iOS程序員肯定是知道要從load方法開(kāi)始找起领炫,因?yàn)檫@個(gè)庫(kù)沒(méi)被引用即可生效,那99%的可能性是使用了這個(gè)iOS中的“黑魔法”张咳。
load方法位于類IQKeyboardManager中帝洪,實(shí)現(xiàn)如下:
+(void)load
{
//Enabling IQKeyboardManager. Loading asynchronous on main thread
[self performSelectorOnMainThread:@selector(sharedManager) withObject:nil waitUntilDone:NO];
}
可以看到,load函數(shù)中脚猾,對(duì)IQKeyboardManager這個(gè)單例實(shí)現(xiàn)了初始化葱峡,初始化重載了init方法。這里看到了我們熟悉的注冊(cè)通知的方法:
[strongSelf registerAllNotifications];
這個(gè)方法的實(shí)現(xiàn)中龙助,最主要的是監(jiān)聽(tīng)了UITextFieldTextDidBeginEditingNotification通知
[self registerTextFieldViewClass:[UITextField class]
didBeginEditingNotificationName:UITextFieldTextDidBeginEditingNotification
didEndEditingNotificationName:UITextFieldTextDidEndEditingNotification];
因此砰奕,當(dāng)UITextField
獲取焦點(diǎn)后,在方法
-(void)textFieldViewDidBeginEditing:(NSNotification*)notification
中進(jìn)行了添加鍵盤工具欄泌参,調(diào)整ScrollView等可能被鍵盤遮擋的View的相應(yīng)屬性脆淹。具體的調(diào)整方法位于
-(void)adjustFrame
中。這也是IQKeyboardManager庫(kù)最重要的方法沽一,也是我們主要分析的方法盖溺。
-(void)adjustFrame
{
UIWindow *keyWindow = [self keyWindow];
//獲取當(dāng)前控制器
UIViewController *rootController = [_textFieldView topMostController];
if (rootController == nil) rootController = [keyWindow topMostWindowController];
//獲取TextField(或TextView)的Frame
CGRect textFieldViewRect = [[_textFieldView superview] convertRect:_textFieldView.frame toView:keyWindow];
CGRect rootViewRect = [[rootController view] frame];
//省略部分代碼....
//獲取移動(dòng)的高度,如果move是正值铣缠,說(shuō)明textField被隱藏烘嘱,否則則是被顯示了
CGFloat move = 0;
if (layoutGuidePosition == IQLayoutGuidePositionBottom)
{
move = CGRectGetMaxY(textFieldViewRect)-(CGRectGetHeight(keyWindow.frame)-kbSize.height);
}
else
{
move = MIN(CGRectGetMinY(textFieldViewRect)-(topLayoutGuide+5), CGRectGetMaxY(textFieldViewRect)-(CGRectGetHeight(keyWindow.frame)-kbSize.height));
}
//省略部分代碼...
//找到視圖中可能存在的ScrollView
UIScrollView *superView = (UIScrollView*)[_textFieldView superviewOfClassType:[UIScrollView class]];
while (superView)
{
if (superView.isScrollEnabled && superView.shouldIgnoreScrollingAdjustment == NO)
{
superScrollView = superView;
break;
}
else
{
// Getting it's superScrollView. // (Enhancement ID: #21, #24)
superView = (UIScrollView*)[superView superviewOfClassType:[UIScrollView class]];
}
}
//以下代碼是為了對(duì)獲取到的ScrollView進(jìn)行判斷是否是當(dāng)前遮蓋鍵盤的ScrollView
if (_lastScrollView){
}{
}
//以下是對(duì)Top Layout guide做的特殊處理(如果大家不熟悉的話,沒(méi)關(guān)系蝗蛙,大概意思就是做個(gè)兼容)
if (layoutGuidePosition == IQLayoutGuidePositionTop)
{
}
else if (layoutGuidePosition == IQLayoutGuidePositionBottom){
}
else//這里是核心的用于處理鍵盤遮擋的代碼
{
//對(duì)textView可以通過(guò)設(shè)置ContentInset來(lái)達(dá)到效果
if ([_textFieldView isKindOfClass:[UITextView class]])
{
UIEdgeInsets newContentInset = textView.contentInset;
newContentInset.bottom = strongSelf.textFieldView.frame.size.height-textViewHeight;
textView.contentInset = newContentInset;
textView.scrollIndicatorInsets = newContentInset;
strongSelf.isTextViewContentInsetChanged = YES;
}
// 這里再對(duì)iPad做個(gè)特殊處理
if ([rootController modalPresentationStyle] == UIModalPresentationFormSheet ||
[rootController modalPresentationStyle] == UIModalPresentationPageSheet)
{
}
else
{
//這里是“核心中的核心”蝇庭,設(shè)置View的Frame,以達(dá)到避免遮擋的目的捡硅。
[self setRootViewFrame:rootViewRect];
}
}
}
好了哮内,以上是對(duì)IQKeyboardManager的使用的介紹以及源碼的分析,希望大家對(duì)這個(gè)非常方便的庫(kù)有個(gè)直觀的印象。當(dāng)然北发,這里筆者只是做了個(gè)簡(jiǎn)單的介紹纹因,如果大家有興趣可以深入的看一下它的源代碼,相信通過(guò)對(duì)IQKeyboardManager源代碼的閱讀琳拨,可以加深對(duì)iOS中的視圖層級(jí)的理解瞭恰。