经过大量的试验和错误,我放弃了,并提出了这个问题。我见过很多人有类似的问题,但不能得到所有的答案工作正确。
我有一个由自定义单元格组成的UITableView。单元格由相邻的5个文本字段组成(有点像网格)。
当我试图滚动和编辑UITableView底部的单元格时,我不能设法让我的单元格正确地定位在键盘上方。
我看到过很多关于改变视图大小的回答……但到目前为止,没有一种效果很好。
谁能用一个具体的代码示例来阐明这样做的“正确”方式?
经过大量的试验和错误,我放弃了,并提出了这个问题。我见过很多人有类似的问题,但不能得到所有的答案工作正确。
我有一个由自定义单元格组成的UITableView。单元格由相邻的5个文本字段组成(有点像网格)。
当我试图滚动和编辑UITableView底部的单元格时,我不能设法让我的单元格正确地定位在键盘上方。
我看到过很多关于改变视图大小的回答……但到目前为止,没有一种效果很好。
谁能用一个具体的代码示例来阐明这样做的“正确”方式?
当前回答
结合并填写几个答案(特别是Ortwin Gentz,用户98013)和另一篇文章中的空白,这将适用于iPad上的SDK 4.3的纵向或横向模式:
@implementation UIView (FindFirstResponder)
- (UIResponder *)findFirstResponder
{
if (self.isFirstResponder) {
return self;
}
for (UIView *subView in self.subviews) {
UIResponder *firstResponder = [subView findFirstResponder];
if (firstResponder != nil) {
return firstResponder;
}
}
return nil;
}
@end
@implementation MyViewController
- (UIResponder *)currentFirstResponder {
return [self.view findFirstResponder];
}
- (IBAction)editingEnded:sender {
[sender resignFirstResponder];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
UITableViewCell *cell = (UITableViewCell*) [[textField superview] superview];
[_tableView scrollToRowAtIndexPath:[_tableView indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
- (void)keyboardWillShow:(NSNotification*)notification {
if ([self currentFirstResponder] != nil) {
NSDictionary* userInfo = [notification userInfo];
// we don't use SDK constants here to be universally compatible with all SDKs ≥ 3.0
NSValue* keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardBoundsUserInfoKey"];
if (!keyboardFrameValue) {
keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardFrameEndUserInfoKey"];
}
// Reduce the tableView height by the part of the keyboard that actually covers the tableView
CGRect windowRect = [[UIApplication sharedApplication] keyWindow].bounds;
CGRect viewRectAbsolute = [_tableView convertRect:_tableView.bounds toView:[[UIApplication sharedApplication] keyWindow]];
CGRect frame = _tableView.frame;
if (UIInterfaceOrientationLandscapeLeft == self.interfaceOrientation ||UIInterfaceOrientationLandscapeRight == self.interfaceOrientation ) {
windowRect = CGRectMake(windowRect.origin.y, windowRect.origin.x, windowRect.size.height, windowRect.size.width);
viewRectAbsolute = CGRectMake(viewRectAbsolute.origin.y, viewRectAbsolute.origin.x, viewRectAbsolute.size.height, viewRectAbsolute.size.width);
}
frame.size.height -= [keyboardFrameValue CGRectValue].size.height - CGRectGetMaxY(windowRect) + CGRectGetMaxY(viewRectAbsolute);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
_tableView.frame = frame;
[UIView commitAnimations];
UITableViewCell *textFieldCell = (id)((UITextField *)self.currentFirstResponder).superview.superview;
NSIndexPath *textFieldIndexPath = [_tableView indexPathForCell:textFieldCell];
// iOS 3 sends hide and show notifications right after each other
// when switching between textFields, so cancel -scrollToOldPosition requests
[NSObject cancelPreviousPerformRequestsWithTarget:self];
_topmostRowBeforeKeyboardWasShown = [[_tableView indexPathsForVisibleRows] objectAtIndex:0];
[_tableView scrollToRowAtIndexPath:textFieldIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
}
- (void) scrollToOldPosition {
[_tableView scrollToRowAtIndexPath:_topmostRowBeforeKeyboardWasShown atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
- (void)keyboardWillHide:(NSNotification*)notification {
if ([self currentFirstResponder] != nil) {
NSDictionary* userInfo = [notification userInfo];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
_tableView.frame = self.view.bounds;
[UIView commitAnimations];
[self performSelector:@selector(scrollToOldPosition) withObject:nil afterDelay:0.1];
}
}
@end
其他回答
我遇到了同样的问题,但注意到它只出现在一个视图中。所以我开始寻找不同的控制器。
我发现滚动行为设置在- (void)viewWillAppear:(BOOL)动画的超级实例。
所以一定要像这样实现:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// your code
}
不管你用的是UIViewController还是UITableViewController;通过将UITableView作为self的子视图来检查它。UIViewController中的视图。这是相同的行为。视图不允许滚动,如果调用[super viewWillAppear:animated];失踪了。
Swift 4.2完整解决方案
我用一组协议创建了GIST,它简化了显示、隐藏或更改键盘时添加额外空间的工作。
特点:
正确使用键盘帧变化(例如,键盘高度变化,如emojii→普通键盘)。 标签栏和工具栏支持UITableView的例子(在其他例子你收到不正确的插入)。 动态动画持续时间(非硬编码)。 面向协议的方法,可以很容易地为您的目的进行修改。
使用
包含一些滚动视图(当然也支持表格视图)的视图控制器的基本用法示例。
class SomeViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addKeyboardFrameChangesObserver()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
removeKeyboardFrameChangesObserver()
}
}
extension SomeViewController: ModifableInsetsOnKeyboardFrameChanges {
var scrollViewToModify: UIScrollView { return scrollView }
}
核心:帧改变观察者
协议KeyboardChangeFrameObserver将在每次键盘帧被改变时触发事件(包括显示,隐藏,帧改变)。
在viewWillAppear()或类似方法调用addKeyboardFrameChangesObserver()。 在viewWillDisappear()或类似的方法调用removeKeyboardFrameChangesObserver()。
实现:滚动视图
ModifableInsetsOnKeyboardFrameChanges协议将UIScrollView支持添加到核心协议。当键盘帧改变时,它改变滚动视图的嵌入。
你的类需要设置滚动视图,一个人的插入将增加/减少键盘帧的变化。
var scrollViewToModify: UIScrollView { get }
Swift 4.2的小变化…
在我的UITableView上,我有很多部分,但我必须避免浮动标题效果,所以我使用了“dummyViewHeight”方法,就像在Stack Overflow上看到的其他地方一样… 这就是我对这个问题的解决方案(它也适用于键盘+工具栏+建议):
将其声明为类常量:
let dummyViewHeight: CGFloat = 40.0
Then
override func viewDidLoad() {
super.viewDidLoad()
//... some stuff here, not needed for this example
// Create non floating header
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: dummyViewHeight))
tableView.contentInset = UIEdgeInsets(top: -dummyViewHeight, left: 0, bottom: 0, right: 0)
addObservers()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
removeObservers()
}
这里所有的魔法……
@objc func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
let keyboardHeight = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue.size.height
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.tableView.bounds.size.width, height: dummyViewHeight))
tableView.contentInset = UIEdgeInsets(top: -dummyViewHeight, left: 0, bottom: keyboardHeight, right: 0)
}
}
@objc func keyboardWillHide(notification: NSNotification) {
UIView.animate(withDuration: 0.25) {
self.tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.tableView.bounds.size.width, height: self.dummyViewHeight))
self.tableView.contentInset = UIEdgeInsets(top: -self.dummyViewHeight, left: 0, bottom: 0, right: 0)
}
}
结合并填写几个答案(特别是Ortwin Gentz,用户98013)和另一篇文章中的空白,这将适用于iPad上的SDK 4.3的纵向或横向模式:
@implementation UIView (FindFirstResponder)
- (UIResponder *)findFirstResponder
{
if (self.isFirstResponder) {
return self;
}
for (UIView *subView in self.subviews) {
UIResponder *firstResponder = [subView findFirstResponder];
if (firstResponder != nil) {
return firstResponder;
}
}
return nil;
}
@end
@implementation MyViewController
- (UIResponder *)currentFirstResponder {
return [self.view findFirstResponder];
}
- (IBAction)editingEnded:sender {
[sender resignFirstResponder];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
UITableViewCell *cell = (UITableViewCell*) [[textField superview] superview];
[_tableView scrollToRowAtIndexPath:[_tableView indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
- (void)keyboardWillShow:(NSNotification*)notification {
if ([self currentFirstResponder] != nil) {
NSDictionary* userInfo = [notification userInfo];
// we don't use SDK constants here to be universally compatible with all SDKs ≥ 3.0
NSValue* keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardBoundsUserInfoKey"];
if (!keyboardFrameValue) {
keyboardFrameValue = [userInfo objectForKey:@"UIKeyboardFrameEndUserInfoKey"];
}
// Reduce the tableView height by the part of the keyboard that actually covers the tableView
CGRect windowRect = [[UIApplication sharedApplication] keyWindow].bounds;
CGRect viewRectAbsolute = [_tableView convertRect:_tableView.bounds toView:[[UIApplication sharedApplication] keyWindow]];
CGRect frame = _tableView.frame;
if (UIInterfaceOrientationLandscapeLeft == self.interfaceOrientation ||UIInterfaceOrientationLandscapeRight == self.interfaceOrientation ) {
windowRect = CGRectMake(windowRect.origin.y, windowRect.origin.x, windowRect.size.height, windowRect.size.width);
viewRectAbsolute = CGRectMake(viewRectAbsolute.origin.y, viewRectAbsolute.origin.x, viewRectAbsolute.size.height, viewRectAbsolute.size.width);
}
frame.size.height -= [keyboardFrameValue CGRectValue].size.height - CGRectGetMaxY(windowRect) + CGRectGetMaxY(viewRectAbsolute);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
_tableView.frame = frame;
[UIView commitAnimations];
UITableViewCell *textFieldCell = (id)((UITextField *)self.currentFirstResponder).superview.superview;
NSIndexPath *textFieldIndexPath = [_tableView indexPathForCell:textFieldCell];
// iOS 3 sends hide and show notifications right after each other
// when switching between textFields, so cancel -scrollToOldPosition requests
[NSObject cancelPreviousPerformRequestsWithTarget:self];
_topmostRowBeforeKeyboardWasShown = [[_tableView indexPathsForVisibleRows] objectAtIndex:0];
[_tableView scrollToRowAtIndexPath:textFieldIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
}
- (void) scrollToOldPosition {
[_tableView scrollToRowAtIndexPath:_topmostRowBeforeKeyboardWasShown atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
- (void)keyboardWillHide:(NSNotification*)notification {
if ([self currentFirstResponder] != nil) {
NSDictionary* userInfo = [notification userInfo];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
_tableView.frame = self.view.bounds;
[UIView commitAnimations];
[self performSelector:@selector(scrollToOldPosition) withObject:nil afterDelay:0.1];
}
}
@end
在viewdidload
-(void)viewdidload{
[super viewdidload];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil];
}
-(void)keyboardWillChange:(NSNotification*)sender{
NSLog(@"keyboardwillchange sender %@",sender);
float margin=0 // set your own topmargin
CGFloat originY = [[sender.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].origin.y;
if (originY >= self.view.frame.size.height){
NSLog(@"keyboardclose");
[tb_ setFrame:CGRectMake(0, margin, self.view.frame.size.width, self.view.frame.size.height-margin)];
}else{
NSLog(@"keyobard on");
float adjustedHeight = self.view.frame.size.height - margin - (self.view.frame.size.height-originY);
[tb_ setFrame:CGRectMake(0, margin, self.view.frame.size.width, adjustedHeight)];
}
}