IQKeyboardManager是一个自动处理键盘弹出、隐藏的三方库,使用非常方便,只需要将库引入工程即可使用,不需要做任何多余的设置,我们在项目中也不需要再单独去监听键盘的willShow和willHide了。
IQKeyboardManager发展到今天已经很成熟了,常见的Bug基本已经解决了。我们今天就简单的记录下IQKeyboardManager的使用和实现原理。
IQKeyboardManager属性含义记录
首先我们先简单记录下常用的属性设置含义,方便以后查询
// 设置键盘自动弹起
[self setEnable:YES];
// 设置键盘和输入框之间的距离
[self setKeyboardDistanceFromTextField:10.0];
// 当键盘弹起时,点击背景,键盘就会收回
[self setShouldResignOnTouchOutside:YES];
// 是否添加键盘上的toolBar
[self setEnableAutoToolbar:YES];
// 是否在toolBar显示输入框中的placeHoder
[self setShouldShowTextFieldPlaceholder:YES];
// 有多个输入框时,可以通过点击Toolbar 上的“前一个”“后一个”按钮来实现移动到不同的输入框
[self setToolbarManageBehaviour:IQAutoToolbarBySubviews];
// 设置toolBar上的颜色文字颜色是否用户自定义
[self setShouldToolbarUsesTextFieldTintColor:NO];
如果在整个项目中引入了IQKeyboardManager,而某一个UIViewController内不使用IQKeyboardManager,可以如下设置:
- (void) viewWillAppear: (BOOL)animated
{
//打开键盘事件相应
[IQKeyboardManager sharedManager].enable = NO;
}
- (void) viewWillDisappear: (BOOL)animated
{
//关闭键盘事件相应
[IQKeyboardManager sharedManager].enable = YES;
}
这样我们就可以在这个UIViewController中自己添加代码监听系统的键盘通知。
如果在某个页面,有多个TextField,而只有某一个TextField不需要IQ键盘上面的toolBar时,只需要设置下其inputAccessoryView
// 如果需要针对某个textField,不需要键盘上的toolBar 可以单独设置
textField.inputAccessoryView = [[UIView alloc] init];
IQKeyboardManager实现原理
我们先观察下IQKeyboardManager的类文件,就会发现核心代码都在IQKeyboardManager.m这个类中,其他的类文件,基本是辅助类。
在IQKeyboardManager.m的init方法中,我们看到它添加了键盘的弹出和消失通知,并且注册了textField和textView的开始编辑和结束编辑的通知
// 键盘将要弹出的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
// 键盘将要隐藏的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
// UITextField开始编辑和结束编辑的通知
[self registerTextFieldViewClass:[UITextField class]
didBeginEditingNotificationName:UITextFieldTextDidBeginEditingNotification didEndEditingNotificationName:UITextFieldTextDidEndEditingNotification];
// UITextView开始编辑和结束编辑的通知
[self registerTextFieldViewClass:[UITextView class] didBeginEditingNotificationName:UITextViewTextDidBeginEditingNotification didEndEditingNotificationName:UITextViewTextDidEndEditingNotification];
正是添加了这4个方法,IQKeyboardManager监听到了一系列通知,并能在-(void)textFieldViewDidBeginEditing:(NSNotification*)notification方法中记录下是哪个控件要开始编辑,并需要键盘弹起通过记录下这个_textFieldView,IQKeyboardManager知道了输入源原来的坐标值,PlaceHolder等值并一一记录下来,方便在键盘发出willShow通知时去调整控件的坐标。
也正是由于知道了这个输入源_textFieldView,IQKeyboardManager才能在-(void)keyboardWillShow:(NSNotification*)aNotification方法中,
- _textFieldView 直接被添加到UIViewController中
借用响应链原理的具体应用[_textFieldView viewController]方法找到_textFieldView被添加进了哪个UIViewController中
// 确定_textFieldView的下一个UIResponder是否为UIViewController,如果是UIViewController类,则返回
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
并通过topMostController确定该UIViewController是否是真的为_textFieldView的所在的VC 。
-(UIViewController *)topMostController
{
NSMutableArray *controllersHierarchy = [[NSMutableArray alloc] init];
UIViewController *topController = self.window.rootViewController;
if (topController)
{
[controllersHierarchy addObject:topController];
}
while ([topController presentedViewController])
{
topController = [topController presentedViewController];
[controllersHierarchy addObject:topController];
}
UIResponder *matchController = [self viewController];
while (matchController != nil && [controllersHierarchy containsObject:matchController] == NO)
{
do
{
matchController = [matchController nextResponder];
} while (matchController != nil && [matchController isKindOfClass:[UIViewController class]] == NO);
}
return (UIViewController*)matchController;
}
如果确定了_textFieldView 只是直接被添加到UIViewController上,则rootViewRect记录下该UIViewController原始坐标,如果有需要则改变rootViewRect的坐标值,并在键盘消失时恢复该UIViewController到原始坐标。
{
// +Positive or zero.
if (move>=0)
{
rootViewRect.origin.y -= move;
// From now prevent keyboard manager to slide up the rootView to more than keyboard height. (Bug ID: #93)
if (_preventShowingBottomBlankSpace == YES)
{
rootViewRect.origin.y = MAX(rootViewRect.origin.y, MIN(0, -kbSize.height+keyboardDistanceFromTextField));
}
[self showLog:@"Moving Upward"];
// Setting adjusted rootViewRect
[self setRootViewFrame:rootViewRect];
_movedDistance = (_topViewBeginRect.origin.y-rootViewRect.origin.y);
}
// -Negative
else
{
CGFloat disturbDistance = CGRectGetMinY(rootViewRect)-CGRectGetMinY(_topViewBeginRect);
// disturbDistance Negative = frame disturbed. Pull Request #3
// disturbDistance positive = frame not disturbed.
if(disturbDistance<0)
{
rootViewRect.origin.y -= MAX(move, disturbDistance);
[self showLog:@"Moving Downward"];
// Setting adjusted rootViewRect
[self setRootViewFrame:rootViewRect];
_movedDistance = (_topViewBeginRect.origin.y-rootViewRect.origin.y);
}
}
}
如果确定了_textFieldView 只是直接被添加到UIScrollView上,则
UIScrollView superView = (UIScrollView)[_textFieldView superviewOfClassType:[UIScrollView class]];记录该UIScrollView为_lastScrollView
else if(superScrollView)
{
_lastScrollView = superScrollView;
_startingContentInsets = superScrollView.contentInset;
_startingContentOffset = superScrollView.contentOffset;
_startingScrollIndicatorInsets = superScrollView.scrollIndicatorInsets;
}
利用UIScrollView的偏移量去主动滑动
superScrollView.contentOffset = CGPointMake(superScrollView.contentOffset.x, shouldOffsetY);
在键盘将要消失时,由于我们已经存储的_lastScrollView就是添加_textFieldView的那个UIScrollView,所以就能恢复原始位置了,这样就实现了_textFieldView随键盘的弹出和消失而自动调整了。
这里只是简单的分析了下输入控件是UITextField,其被添加到UIScrollView 和 UIViewController中的场景,其他场景实现原理基本类似,就不在写了。
如果你喜欢我的创作,请点赞! 你的赞是我继续创作的动力,谢谢!如果不喜欢,请留言,谢谢!