键盘出现时如何滚动 UIScrollView?
Posted
技术标签:
【中文标题】键盘出现时如何滚动 UIScrollView?【英文标题】:How do I scroll the UIScrollView when the keyboard appears? 【发布时间】:2012-10-21 03:08:24 【问题描述】:我的代码有问题。当我编辑应该被键盘弹出隐藏的UITextField
时,我正在尝试移动UIScrollView
。
我现在正在移动主框架,因为我不知道如何在代码中“向上滚动”。
所以,我做了一些代码,它工作正常,但是当我编辑一个 UItextfield 并切换到另一个 UITextField
而不按“返回”按钮时,主视图会变得很远。
我用我的变量大小、距离和 textFieldRect.origin.y 做了一个NSLog()
,如下所示。当我将两个 UITextField
放在同一个位置(y 原点)并执行此特定的“开关”(不按回车键)时,我得到相同的数字,而我的代码在第一个 UITextField
编辑时工作正常,但不适用于第二次编辑。
看看这个:
- (void)textFieldDidBeginEditing:(UITextField *)textField
int size;
CGRect textFieldRect = [self.view.window convertRect:textField.bounds fromView:textField];
size = textFieldRect.origin.y + textFieldRect.size.height;
if (change == FALSE)
size = size - distance;
if (size < PORTRAIT_KEYBOARD_HEIGHT)
distance = 0;
else if (size > PORTRAIT_KEYBOARD_HEIGHT)
distance = size - PORTRAIT_KEYBOARD_HEIGHT + 5; // +5 px for more visibility
NSLog(@"origin %f", textFieldRect.origin.y);
NSLog(@"size %d", size);
NSLog(@"distance %d", distance);
CGRect viewFrame = self.view.frame;
viewFrame.origin.y -= distance;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];
[self.view setFrame:viewFrame];
[UIView commitAnimations];
change = FALSE;
- (void)textFieldDidEndEditing:(UITextField *)textField
change = TRUE;
CGRect viewFrame = self.view.frame;
viewFrame.origin.y += distance;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];
[self.view setFrame:viewFrame];
[UIView commitAnimations];
有什么想法吗?
【问题讨论】:
【参考方案1】:Apple 推荐的方法是更改UIScrollView
的contentInset
。这是一个非常优雅的解决方案,因为您不必弄乱contentSize
。
以下代码是从Keyboard Programming Guide 复制的,其中解释了此问题的处理。你应该看看它。
// Call this method somewhere in your view controller setup code.
- (void)registerForKeyboardNotifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
// If active text field is hidden by keyboard, scroll it so it's visible
// Your application might not need or want this behavior.
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
if (!CGRectContainsPoint(aRect, activeField.frame.origin) )
CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y-kbSize.height);
[scrollView setContentOffset:scrollPoint animated:YES];
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
Swift 版本:
func registerForKeyboardNotifications()
NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardAppear(_:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardDisappear(_:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil)
// Don't forget to unregister when done
deinit
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardDidHide, object: nil)
@objc func onKeyboardAppear(_ notification: NSNotification)
let info = notification.userInfo!
let rect: CGRect = info[UIKeyboardFrameBeginUserInfoKey] as! CGRect
let kbSize = rect.size
let insets = UIEdgeInsetsMake(0, 0, kbSize.height, 0)
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
// If active text field is hidden by keyboard, scroll it so it's visible
// Your application might not need or want this behavior.
var aRect = self.view.frame;
aRect.size.height -= kbSize.height;
let activeField: UITextField? = [addressTextView, servicePathTextView, usernameTextView, passwordTextView].first $0.isFirstResponder
if let activeField = activeField
if !aRect.contains(activeField.frame.origin)
let scrollPoint = CGPoint(x: 0, y: activeField.frame.origin.y-kbSize.height)
scrollView.setContentOffset(scrollPoint, animated: true)
@objc func onKeyboardDisappear(_ notification: NSNotification)
scrollView.contentInset = UIEdgeInsets.zero
scrollView.scrollIndicatorInsets = UIEdgeInsets.zero
【讨论】:
啊,好的。对不起,我没有明白你在谈论滚动部分。是的,activeField 只是 UITextField 属性的占位符。所以替换它并再试一次。您不需要更改大小,否则 textField 的高度会变大。 您实际上并不想覆盖现有的 contentInsets.top,如果这样做,您的视图可能会滑到 Navigation 后面。 github.com/michaeltyson/TPKeyboardAvoiding 这是超级简单的解决方案 最好将activeField.frame
转换为相对框架,因为activeField
不必是self.view
的直接子级。更新后的代码应类似于:CGRect aRect = self.view.frame; aRect.size.height -= kbSize.height; CGRect relativeFieldFrame = [activeField convertRect:activeField.frame toView:self.view]; if (!CGRectContainsPoint(aRect, relativeFieldFrame.origin) ) CGPoint scrollPoint = CGPointMake(0.0, relativeFieldFrame.origin.y-kbSize.height); [self.mainView.scrollView setContentOffset:scrollPoint animated:YES];
我不得不在 ios 11 上使用 UIKeyboardFrameEndUserInfoKey
键,因为 UIKeyboardFrameBeginUserInfoKey
通常会给我一个零高度。【参考方案2】:
我刚刚在 Xcode 7(beta 6)上使用适用于 iOS9 的 Swift 2.0 实现了这一点,在这里可以正常工作。
override func viewWillAppear(animated: Bool)
super.viewWillAppear(animated)
registerKeyboardNotifications()
func registerKeyboardNotifications()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
deinit
NSNotificationCenter.defaultCenter().removeObserver(self)
func keyboardWillShow(notification: NSNotification)
let userInfo: NSDictionary = notification.userInfo!
let keyboardSize = userInfo.objectForKey(UIKeyboardFrameBeginUserInfoKey)!.CGRectValue.size
let contentInsets = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
var viewRect = view.frame
viewRect.size.height -= keyboardSize.height
if CGRectContainsPoint(viewRect, textField.frame.origin)
let scrollPoint = CGPointMake(0, textField.frame.origin.y - keyboardSize.height)
scrollView.setContentOffset(scrollPoint, animated: true)
func keyboardWillHide(notification: NSNotification)
scrollView.contentInset = UIEdgeInsetsZero
scrollView.scrollIndicatorInsets = UIEdgeInsetsZero
为 Swift 3 编辑
似乎您只需要使用 Swift 3 设置 contentInset
和 scrollIndicatorInset
,滚动/contentOffset 会自动完成..
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
registerKeyboardNotifications()
func registerKeyboardNotifications()
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillShow(notification:)),
name: NSNotification.Name.UIKeyboardWillShow,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillHide(notification:)),
name: NSNotification.Name.UIKeyboardWillHide,
object: nil)
deinit
NotificationCenter.default.removeObserver(self)
func keyboardWillShow(notification: NSNotification)
let userInfo: NSDictionary = notification.userInfo! as NSDictionary
let keyboardInfo = userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue
let keyboardSize = keyboardInfo.cgRectValue.size
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
func keyboardWillHide(notification: NSNotification)
scrollView.contentInset = .zero
scrollView.scrollIndicatorInsets = .zero
【讨论】:
您能解释一下您的代码吗?我有两个文本字段,一旦我编辑第一个文本字段,它会向下滚动,而第二个文本字段会向上滚动。 在 iPad 上,这会将滚动视图向下而不是向上移动。知道那里发生了什么吗? @can 引用的 textField 是您根据当前第一响应者为视图控制器设置的变量 我想知道为什么 Apple 现在会主动为我们滚动键盘上方的活动文本字段。 在swift 4中将@objc放在keyboardWillShow和keyboardWillHide方法上【参考方案3】:Swift 5解决方案:
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
registerKeyboardNotifications()
func registerKeyboardNotifications()
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillShow(notification:)),
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillHide(notification:)),
name: UIResponder.keyboardWillHideNotification,
object: nil)
override func viewWillDisappear(_ animated: Bool)
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
@objc func keyboardWillShow(notification: NSNotification)
let userInfo: NSDictionary = notification.userInfo! as NSDictionary
let keyboardInfo = userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue
let keyboardSize = keyboardInfo.cgRectValue.size
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
@objc func keyboardWillHide(notification: NSNotification)
scrollView.contentInset = .zero
scrollView.scrollIndicatorInsets = .zero
【讨论】:
大部分工作,只需将UIKeyboardFrameBeginUserInfoKey
更改为UIKeyboardFrameEndUserInfoKey
我在 UITableView 中应用了这段代码。但是 TableView 不会立即向上滚动。
UIKeyboardWillShow
、UIKeyboardWillHide
和 UIKeyboardFrameBeginUserInfoKey
已重命名为 UIResponder.keyboardWillShowNotification
、UIResponder.keyboardWillHideNotification
和 UIResponder.keyboardFrameBeginUserInfoKey
。【参考方案4】:
这里的所有答案似乎都忘记了景观的可能性。如果您希望设备旋转到横向视图时也能正常工作,那么您将面临问题。
这里的诀窍是,虽然视图知道方向,但键盘不知道。这意味着在横向中,键盘的宽度实际上就是它的高度,反之亦然。
要修改 Apple 推荐的更改内容插入的方式并使其支持横向,我建议使用以下方法:
// Call this method somewhere in your view controller setup code.
- (void)registerForKeyboardNotifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
CGSize keyboardSize = [[[notif userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
if (orientation == UIDeviceOrientationLandscapeLeft || orientation == UIDeviceOrientationLandscapeRight )
CGSize origKeySize = keyboardSize;
keyboardSize.height = origKeySize.width;
keyboardSize.width = origKeySize.height;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0);
scroller.contentInset = contentInsets;
scroller.scrollIndicatorInsets = contentInsets;
// If active text field is hidden by keyboard, scroll it so it's visible
// Your application might not need or want this behavior.
CGRect rect = scroller.frame;
rect.size.height -= keyboardSize.height;
NSLog(@"Rect Size Height: %f", rect.size.height);
if (!CGRectContainsPoint(rect, activeField.frame.origin))
CGPoint point = CGPointMake(0, activeField.frame.origin.y - keyboardSize.height);
NSLog(@"Point Height: %f", point.y);
[scroller setContentOffset:point animated:YES];
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
这里要注意的部分如下:
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
CGSize keyboardSize = [[[notif userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
if (orientation == UIDeviceOrientationLandscapeLeft || orientation == UIDeviceOrientationLandscapeRight )
CGSize origKeySize = keyboardSize;
keyboardSize.height = origKeySize.width;
keyboardSize.width = origKeySize.height;
它的作用是检测设备的方向。如果是横向,它将“交换”keyboardSize 变量的宽度和高度值,以确保在每个方向上使用正确的值。
【讨论】:
好吧,如果滚动条占据屏幕的整个高度 UIEdgeInsetsMake(0.0, 0.0, kbSize.高度 - ([UIScreen mainScreen].bounds.size.height - cvf.origin.y - cvf.size.height), 0.0);其中 cvf 是 scroller.frame if (orientation == UIInterfaceOrientationLandscapeLeft ||orientation == UIInterfaceOrientationLandscapeRight)【参考方案5】:对于这些东西不需要大量的编码,就像下面的代码一样简单:-
您在 UIScrollview 中的所有文本文件都来自 nib,如下图所示:-
YourViewController.h
@interface cntrInquiryViewController : UIViewController<UIScrollViewDelegate,UITextFieldDelegate>
IBOutlet UITextField *txtName;
IBOutlet UITextField *txtEmail;
IBOutlet UIScrollView *srcScrollView;
@end
从 nib 连接 IBOutlet,并从 NIB 连接 UItextfiled 的每个委托和 scrollview 委托
-(void)viewWillAppear:(BOOL)animated
srcScrollView.contentSize = CGSizeMake(320, 500);
[super viewWillAppear:YES];
-(void)textFieldDidBeginEditing:(FMTextField *)textField
[srcScrollView setContentOffset:CGPointMake(0,textField.center.y-140) animated:YES];//you can set your y cordinate as your req also
-(BOOL)textFieldShouldReturn:(UITextField *)textField
[textField resignFirstResponder];
[srcScrollView setContentOffset:CGPointMake(0,0) animated:YES];
return YES;
注意如果没有连接文本文件的委托,那么没有一种方法可以工作,请确保所有 iBOulate 和委托都正确连接
【讨论】:
这是一种非常古老的方法,不应该使用。它依赖于不再相关的硬编码值和假设。 @Womble 有很多旧的但与当时的问题相关的答案(当问题被提出时)。所以,不要无故投反对票。【参考方案6】:Apple 的建议在 Swift 中重新编码 + 在 iOS 中使用 UIScrollView 和自动布局(基于以下链接:link 1、link 2、link 3):
import UIKit
class ViewController: UIViewController, UITextFieldDelegate
@IBOutlet var t1: UITextField!
@IBOutlet var t2: UITextField!
@IBOutlet var t3: UITextField!
@IBOutlet var t4: UITextField!
@IBOutlet var srcScrollView: UIScrollView!
@IBOutlet var contentView: UIView!
var contentViewCoordinates: CGPoint!
override func viewDidLoad()
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
/* Constraints on content view */
let leftConstraint = NSLayoutConstraint(item:self.contentView,
attribute:NSLayoutAttribute.Leading,
relatedBy:NSLayoutRelation.Equal,
toItem:self.view,
attribute:NSLayoutAttribute.Left,
multiplier:1.0,
constant:0)
self.view.addConstraint(leftConstraint)
let rightConstraint = NSLayoutConstraint(item:self.contentView,
attribute:NSLayoutAttribute.Trailing,
relatedBy:NSLayoutRelation.Equal,
toItem:self.view,
attribute:NSLayoutAttribute.Right,
multiplier:1.0,
constant:0)
self.view.addConstraint(rightConstraint)
/* Tap gesture */
let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "hideKeyboard")
// prevents the scroll view from swallowing up the touch event of child buttons
tapGesture.cancelsTouchesInView = false
srcScrollView.addGestureRecognizer(tapGesture)
/* Save content view coordinates */
contentViewCoordinates = contentView.frame.origin
func hideKeyboard()
t1.resignFirstResponder()
t2.resignFirstResponder()
t3.resignFirstResponder()
t4.resignFirstResponder()
var activeField: UITextField?
func textFieldDidBeginEditing(textField: UITextField)
activeField = textField
func textFieldDidEndEditing(textField: UITextField)
activeField = nil
override func viewWillAppear(animated: Bool)
super.viewWillAppear(animated)
let center = NSNotificationCenter.defaultCenter()
center.addObserver(self, selector: "keyboardOnScreen:", name: UIKeyboardDidShowNotification, object: nil)
center.addObserver(self, selector: "keyboardOffScreen:", name: UIKeyboardDidHideNotification, object: nil)
func keyboardOnScreen(notification: NSNotification)
// Retrieve the size and top margin (inset is the fancy word used by Apple)
// of the keyboard displayed.
let info: NSDictionary = notification.userInfo!
let kbSize = info.valueForKey(UIKeyboardFrameEndUserInfoKey)?.CGRectValue().size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize!.height, 0.0)
srcScrollView.contentInset = contentInsets
srcScrollView.scrollIndicatorInsets = contentInsets
var aRect: CGRect = self.view.frame
aRect.size.height -= kbSize!.height
//you may not need to scroll, see if the active field is already visible
if (CGRectContainsPoint(aRect, activeField!.frame.origin) == false)
let scrollPoint:CGPoint = CGPointMake(0.0, activeField!.frame.origin.y - kbSize!.height)
srcScrollView.setContentOffset(scrollPoint, animated: true)
// func keyboardOnScreen(aNotification: NSNotification)
// let info: NSDictionary = aNotification.userInfo!
// let kbSize = info.valueForKey(UIKeyboardFrameEndUserInfoKey)?.CGRectValue().size
//
// var bkgndRect: CGRect! = activeField?.superview?.frame
//
// bkgndRect.size.height += kbSize!.height
//
// activeField?.superview?.frame = bkgndRect
//
// srcScrollView.setContentOffset(CGPointMake(0.0, activeField!.frame.origin.y - kbSize!.height), animated: true)
//
func keyboardOffScreen(notification: NSNotification)
let contentInsets:UIEdgeInsets = UIEdgeInsetsZero
srcScrollView.contentInset = contentInsets
srcScrollView.scrollIndicatorInsets = contentInsets
self.srcScrollView.setContentOffset(CGPointMake(0, -self.view.frame.origin.y/2), animated: true)
【讨论】:
经过一天的努力,我设法在 Swift 中实现了苹果的建议 + 稍微修改了他们的代码 + 对其进行了调整,使其在所有苹果设备上都能响应,所以在投反对票之前,请告诉我为什么.上面的代码都不能在所有设备和所有布局情况下正常工作。但是我的确实适用于所有苹果设备和布局情况。 我想您还需要在视图消失之前取消订阅通知中心?【参考方案7】:Swift 4.2 解决方案,考虑了 UIToolbar 和 UITabBar 的可能高度。
private func setupKeyboardNotifications()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIControl.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIControl.keyboardWillHideNotification, object: nil)
@objc func keyboardWillShow(_ notification: Notification)
let userInfo: NSDictionary = notification.userInfo! as NSDictionary
let keyboardSize = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.size
let tabbarHeight = tabBarController?.tabBar.frame.size.height ?? 0
let toolbarHeight = navigationController?.toolbar.frame.size.height ?? 0
let bottomInset = keyboardSize.height - tabbarHeight - toolbarHeight
scrollView.contentInset.bottom = bottomInset
scrollView.scrollIndicatorInsets.bottom = bottomInset
@objc func keyboardWillHide(_ notification: Notification)
scrollView.contentInset = .zero
scrollView.scrollIndicatorInsets = .zero
而且,如果你的目标是 Joe)
【讨论】:
iPhone X 是否考虑到标签栏下的空间?这是 iOS 条纹出现的地方。 iOS9+ 不需要移除观察者developer.apple.com/documentation/foundation/notificationcenter/… 是的,你是对的。我已经相应地更新了答案。【参考方案8】:我要在 Apple 代码中更新的唯一内容是 keyboardWillBeHidden: 方法,以提供平滑过渡。
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
[UIView animateWithDuration:0.4 animations:^
self.scrollView.contentInset = contentInsets;
];
self.scrollView.scrollIndicatorInsets = contentInsets;
【讨论】:
【参考方案9】:这是一个兼容 Swift 3 的答案,它也适用于导航控制器中的视图控制器 - 因为它们会更改滚动视图 contentInset.top
属性。
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
self.registerKeyboardNotifications()
override func viewWillDisappear(_ animated: Bool)
super.viewWillDisappear(animated)
self.unregisterKeyboardNotifications()
func registerKeyboardNotifications()
NotificationCenter.default.addObserver(self, selector: #selector(LoginViewController.keyboardDidShow(notification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(LoginViewController.keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
func unregisterKeyboardNotifications()
NotificationCenter.default.removeObserver(self)
func keyboardDidShow(notification: NSNotification)
let userInfo: NSDictionary = notification.userInfo! as NSDictionary
let keyboardInfo = userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue
let keyboardSize = keyboardInfo.cgRectValue.size
// Get the existing contentInset for the scrollView and set the bottom property to be the height of the keyboard
var contentInset = self.scrollView.contentInset
contentInset.bottom = keyboardSize.height
self.scrollView.contentInset = contentInset
self.scrollView.scrollIndicatorInsets = contentInset
func keyboardWillHide(notification: NSNotification)
var contentInset = self.scrollView.contentInset
contentInset.bottom = 0
self.scrollView.contentInset = contentInset
self.scrollView.scrollIndicatorInsets = UIEdgeInsets.zero
【讨论】:
【参考方案10】:我发现上面的答案已经过时了。滚动时也不完美。
这是一个快速版本。
它将在文本字段的正下方滚动,没有多余的空间。它会恢复到它第一次出现时的样子。
//add observer
override func viewDidLoad()
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ARVHttpPlayVC.keyboardDidShow(_:)), name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ARVHttpPlayVC.keyboardDidHide(_:)), name: UIKeyboardDidHideNotification, object: nil)
func keyboardDidShow(notification: NSNotification)
let userInfo: NSDictionary = notification.userInfo!
let keyboardSize = userInfo.objectForKey(UIKeyboardFrameEndUserInfoKey)!.CGRectValue.size
let difference = keyboardSize.height - (self.view.frame.height - inputTextField.frame.origin.y - inputTextField.frame.size.height)
if difference > 0
var contentInset:UIEdgeInsets = self.scrollView.contentInset
contentInset.bottom = difference
self.scrollView.contentInset = contentInset
let scrollPoint = CGPointMake(0, difference)
self.scrollView.setContentOffset(scrollPoint, animated: true)
func keyboardDidHide(notification: NSNotification)
let contentInset:UIEdgeInsets = UIEdgeInsetsZero
self.scrollView.contentInset = contentInset
//remove observer
deinit
NSNotificationCenter.defaultCenter().removeObserver(self)
【讨论】:
它确实向上滚动了一点,但不足以暴露整个高度。我正在使用 Swift 2.3 并在 iphone 5 上对其进行测试。【参考方案11】:这是我一直在使用的。这很简单,而且效果很好。
#pragma mark - Scrolling
-(void)scrollElement:(UIView *)view toPoint:(float)y
CGRect theFrame = view.frame;
float orig_y = theFrame.origin.y;
float diff = y - orig_y;
if (diff < 0)
[self scrollToY:diff];
else
[self scrollToY:0];
-(void)scrollToY:(float)y
[UIView animateWithDuration:0.3f animations:^
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
self.view.transform = CGAffineTransformMakeTranslation(0, y);
];
使用UITextField
委托调用textFieldDidBeginEditing:
向上移动您的视图,并添加通知观察者以在键盘隐藏时使视图恢复正常:
-(void)textFieldDidBeginEditing:(UITextField *)textField
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
if (self.view.frame.origin.y == 0)
[self scrollToY:-90.0]; // y can be changed to your liking
-(void)keyboardWillHide:(NSNotification*)note
[self scrollToY:0];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
【讨论】:
【参考方案12】:这是 Swift
改进的最终代码 //MARK: UITextFieldDelegate
func textFieldDidBeginEditing(textField: UITextField!) //delegate method
self.textField = textField
func textFieldShouldReturn(textField: UITextField!) -> Bool //delegate method
textField.resignFirstResponder()
return true
//MARK: Keyboard handling
override func viewWillDisappear(animated: Bool)
super.viewWillDisappear(animated)
unregisterKeyboardNotifications()
func registerKeyboardNotifications()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(UCProfileSettingsViewController.keyboardDidShow(_:)), name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(UCProfileSettingsViewController.keyboardWillHide(_:)), name: UIKeyboardWillHideNotification, object: nil)
func unregisterKeyboardNotifications()
NSNotificationCenter.defaultCenter().removeObserver(self)
func keyboardDidShow(notification: NSNotification)
let userInfo: NSDictionary = notification.userInfo!
let keyboardSize = userInfo.objectForKey(UIKeyboardFrameBeginUserInfoKey)!.CGRectValue.size
let contentInsets = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
var viewRect = self.view.frame
viewRect.size.height -= keyboardSize.height
let relativeFieldFrame: CGRect = textField.convertRect(textField.frame, toView: self.view)
if CGRectContainsPoint(viewRect, relativeFieldFrame.origin)
let scrollPoint = CGPointMake(0, relativeFieldFrame.origin.y - keyboardSize.height)
scrollView.setContentOffset(scrollPoint, animated: true)
func keyboardWillHide(notification: NSNotification)
scrollView.contentInset = UIEdgeInsetsZero
scrollView.scrollIndicatorInsets = UIEdgeInsetsZero
【讨论】:
我认为你的逻辑是相反的 - 如果 CGRectContainsPoint 不包含 relativeFieldFrame.origin,你只想滚动。【参考方案13】:最简单的解决方案之一是使用以下协议:
protocol ScrollViewKeyboardDelegate: class
var scrollView: UIScrollView? get set
func registerKeyboardNotifications()
func unregisterKeyboardNotifications()
extension ScrollViewKeyboardDelegate where Self: UIViewController
func registerKeyboardNotifications()
NotificationCenter.default.addObserver(
forName: UIResponder.keyboardWillChangeFrameNotification,
object: nil,
queue: nil) [weak self] notification in
self?.keyboardWillBeShown(notification)
NotificationCenter.default.addObserver(
forName: UIResponder.keyboardWillHideNotification,
object: nil,
queue: nil) [weak self] notification in
self?.keyboardWillBeHidden(notification)
func unregisterKeyboardNotifications()
NotificationCenter.default.removeObserver(
self,
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil
)
NotificationCenter.default.removeObserver(
self,
name: UIResponder.keyboardWillHideNotification,
object: nil
)
func keyboardWillBeShown(_ notification: Notification)
let info = notification.userInfo
let key = (info?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)
let aKeyboardSize = key?.cgRectValue
guard let keyboardSize = aKeyboardSize,
let scrollView = self.scrollView else
return
let bottomInset = keyboardSize.height
scrollView.contentInset.bottom = bottomInset
scrollView.scrollIndicatorInsets.bottom = bottomInset
if let activeField = self.view.firstResponder
let yPosition = activeField.frame.origin.y - bottomInset
if yPosition > 0
let scrollPoint = CGPoint(x: 0, y: yPosition)
scrollView.setContentOffset(scrollPoint, animated: true)
func keyboardWillBeHidden(_ notification: Notification)
self.scrollView?.contentInset = .zero
self.scrollView?.scrollIndicatorInsets = .zero
extension UIView
var firstResponder: UIView?
guard !isFirstResponder else return self
return subviews.first(where: $0.firstResponder != nil )
当你想使用这个协议时,你只需要遵守它并在你的控制器中分配你的滚动视图,如下所示:
class MyViewController: UIViewController
@IBOutlet var scrollViewOutlet: UIScrollView?
var scrollView: UIScrollView?
public override func viewDidLoad()
super.viewDidLoad()
self.scrollView = self.scrollViewOutlet
self.scrollView?.isScrollEnabled = true
self.registerKeyboardNotifications()
extension MyViewController: ScrollViewKeyboardDelegate
deinit
self.unregisterKeyboardNotifications()
【讨论】:
【参考方案14】:我会这样做。这是很多代码,但它确保当前焦点的 textField 在“可用空间”中垂直居中:
- (void)viewWillAppear:(BOOL)animated
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
- (void)viewWillDisappear:(BOOL)animated
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
- (void)keyboardWillShow:(NSNotification *)notification
NSDictionary *info = [notification userInfo];
NSValue *keyBoardEndFrame = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
CGSize keyboardSize = [keyBoardEndFrame CGRectValue].size;
self.keyboardSize = keyboardSize;
[self adjustScrollViewOffsetToCenterTextField:self.currentTextField];
- (void)keyboardWillHide:(NSNotification *)notification
self.keyboardSize = CGSizeZero;
- (IBAction)textFieldGotFocus:(UITextField *)sender
sender.inputAccessoryView = self.keyboardAccessoryView;
self.currentTextField = sender;
[self adjustScrollViewOffsetToCenterTextField:sender];
- (void)adjustScrollViewOffsetToCenterTextField:(UITextField *)textField
CGRect textFieldFrame = textField.frame;
float keyboardHeight = MIN(self.keyboardSize.width, self.keyboardSize.height);
float visibleScrollViewHeight = self.scrollView.frame.size.height - keyboardHeight;
float offsetInScrollViewCoords = (visibleScrollViewHeight / 2) - (textFieldFrame.size.height / 2);
float scrollViewOffset = textFieldFrame.origin.y - offsetInScrollViewCoords;
[UIView animateWithDuration:.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^
self.scrollView.contentOffset = CGPointMake(self.scrollView.contentOffset.x, scrollViewOffset);
completion:NULL];
you'll need these two properties in your @interface...
@property (nonatomic, assign) CGSize keyboardSize;
@property (nonatomic, strong) UITextField *currentTextField;
请注意,- (IBAction)textFieldGotFocus:
操作与每个 textField 的 DidBeginEditing
状态相关联。
另外,从键盘通知中获取动画持续时间并将其用于滚动视图动画而不是固定值会更好一些,但请告我,这对我来说已经足够了 ;)
【讨论】:
【参考方案15】:如果您不想计算太多,请使用以下扩展:
func scrollSubviewToBeVisible(subview: UIView, animated: Bool)
let visibleFrame = UIEdgeInsetsInsetRect(self.bounds, self.contentInset)
let subviewFrame = subview.convertRect(subview.bounds, toView: self)
if (!CGRectContainsRect(visibleFrame, subviewFrame))
self.scrollRectToVisible(subviewFrame, animated: animated)
也许你想让你的 UITextField 始终可见:
func textViewDidChange(textView: UITextView)
self.scrollView?.scrollSubviewToBeVisible(textView, animated: false)
【讨论】:
【参考方案16】:在 Swift 3 中试试这个代码:
override func viewDidAppear(_ animated: Bool)
setupViewResizerOnKeyboardShown()
func setupViewResizerOnKeyboardShown()
NotificationCenter.default.addObserver(self,
selector: #selector(self.keyboardWillShowForResizing),
name: Notification.Name.UIKeyboardWillShow,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(self.keyboardWillHideForResizing),
name: Notification.Name.UIKeyboardWillHide,
object: nil)
func keyboardWillShowForResizing(notification: Notification)
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
let window = self.view.window?.frame
// We're not just minusing the kb height from the view height because
// the view could already have been resized for the keyboard before
self.view.frame = CGRect(x: self.view.frame.origin.x,
y: self.view.frame.origin.y,
width: self.view.frame.width,
height: window.origin.y + window.height - keyboardSize.height)
else
debugPrint("We're showing the keyboard and either the keyboard size or window is nil: panic widely.")
func keyboardWillHideForResizing(notification: Notification)
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let viewHeight = self.view.frame.height
self.view.frame = CGRect(x: self.view.frame.origin.x,
y: self.view.frame.origin.y,
width: self.view.frame.width,
height: viewHeight) //viewHeight + keyboardSize.height
else
debugPrint("We're about to hide the keyboard and the keyboard size is nil. Now is the rapture.")
deinit
NotificationCenter.default.removeObserver(self)
【讨论】:
【参考方案17】:Swift 5 解决方案基于上述Masa solution - 与之相关的更改:
使用keyboardFrameEndUserInfoKey
代替
keyboardFrameBeginUserInfoKey
,因为
keyboardFrameBeginUserInfoKey
可以在第一次显示返回其他值
如此处所述:keyboard height varies when appearing
使用“will”而不是“did”通知并将其更改为 Swift 5 键名称:UIResponder.keyboardWillShowNotification
/UIResponder.keyboardWillHideNotification
而不是 NSNotification.Name.UIKeyboardDidShow
/NSNotification.Name.UIKeyboardDidHide
代码:
override func viewDidLoad()
super.viewDidLoad()
registerForKeyboardNotifications()
func registerForKeyboardNotifications()
NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardAppear(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardDisappear(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
@objc func onKeyboardAppear(_ notification: NSNotification)
guard let info = notification.userInfo, let kbSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size else return
let insets = UIEdgeInsets(top: 0, left: 0, bottom: kbSize.height, right: 0)
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
//Other changes if needed
deinit
NotificationCenter.default.removeObserver(self)
【讨论】:
您的答案中缺少函数onKeyboardDisappear
:)【参考方案18】:
您实际上不需要 UIScrollView 来执行此操作。我使用了这段代码,它对我有用:
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField
if (textField==_myTextField)
[self keyBoardAppeared];
return true;
-(void)textFieldDidEndEditing:(UITextField *)textField
if (textField==_myTextField)
[self keyBoardDisappeared];
-(void) keyBoardAppeared
CGRect frame = self.view.frame;
[UIView animateWithDuration:0.3
delay:0
options: UIViewAnimationCurveEaseOut
animations:^
self.view.frame = CGRectMake(frame.origin.x, frame.origin.y-215, frame.size.width, frame.size.height);
completion:^(BOOL finished)
];
-(void) keyBoardDisappeared
CGRect frame = self.view.frame;
[UIView animateWithDuration:0.3
delay:0
options: UIViewAnimationCurveEaseOut
animations:^
self.view.frame = CGRectMake(frame.origin.x, frame.origin.y+215, frame.size.width, frame.size.height);
completion:^(BOOL finished)
];
【讨论】:
问题:使用硬编码值。上下移动整个 UI,这通常会弄乱视图的外观。【参考方案19】:您可以使用UIScrollView
中的属性contentOffset
进行滚动,例如,
CGPoint offset = scrollview.contentOffset;
offset.y -= KEYBOARD_HEIGHT + 5;
scrollview.contentOffset = offset;
还有一种方法可以进行动画滚动。
至于您的第二次编辑没有正确滚动的原因,可能是因为您似乎认为每次编辑开始时都会出现一个新键盘。您可以尝试检查是否已经针对“键盘”可见位置进行了调整(同样在还原之前检查键盘可见性)。
更好的解决方案可能是监听键盘通知,例如:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
【讨论】:
【参考方案20】:我现在知道这是一个老问题,但我认为它可能对其他人有所帮助。我想为我拥有的一些应用程序实现一些更容易的东西,所以我为此创建了一个类。如果需要,可以在这里下载:https://github.com/sdernley/iOSTextFieldHandler
就像将所有 UITextField 设置为具有 self 的委托一样简单
textfieldname.delegate = self;
然后将其添加到您的视图控制器中,并使用您的滚动视图和提交按钮的名称
- (void)textFieldDidBeginEditing:(UITextField *)textField
[iOSTextFieldHandler TextboxKeyboardMover:containingScrollView tf:textField btn:btnSubmit];
【讨论】:
【参考方案21】:以下是我的有效解决方案(5 个步骤)
Step1:添加一个观察者来捕捉哪个 UITEXTFIELD 或 UITEXTVIEW ShoudBeginEditing(其中对象被初始化或 ViewDidLoad。
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateActiveField:)
name:@"UPDATE_ACTIVE_FIELD" object:nil];
Step2:当..ShouldBeginEditing 使用UITEXTFIELD 或UITEXTVIEW 的OBJECT 时发布通知
-(BOOL)textViewShouldBeginEditing:(UITextView *)textView
[[NSNotificationCenter defaultCenter] postNotificationName:@"UPDATE_ACTIVE_FIELD"
object:textView];
return YES;
Step3:(Step1调用)分配当前UITEXTFIELD或UITEXTVIEW的方法
-(void) updateActiveField: (id) sender
activeField = [sender object];
Step4:添加键盘观察器UIKeyboardWillShowNotification(与Step1相同)
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
和方法:
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
_currentEdgeInsets = self.layoutPanel.contentInset; // store current insets to restore them later
self.layoutPanel.contentInset = contentInsets;
self.layoutPanel.scrollIndicatorInsets = contentInsets;
// If active text field is hidden by keyboard, scroll it so it's visible
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
CGPoint p = [activeField convertPoint:activeField.bounds.origin toView:window];
if (!CGRectContainsPoint(aRect, p) )
CGPoint scrollPoint = CGPointMake(0.0, activeField.frame.origin.y +kbSize.height);
[self.layoutPanel setContentOffset:scrollPoint animated:YES];
self.layoutPanel.scrollEnabled = NO;
Step5:添加键盘观察器UIKeyboardWillHideNotification(与步骤1相同)
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
和方法:
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
self.layoutPanel.contentInset = _currentEdgeInsets;
self.layoutPanel.scrollIndicatorInsets = _currentEdgeInsets;
self.layoutPanel.scrollEnabled = YES;
记得移除观察者!
【讨论】:
【参考方案22】:我使用了 Sudheer Palchuri 提供的这个答案 https://***.com/users/2873919/sudheer-palchuri https://***.com/a/32583809/6193496
在 ViewDidLoad 中,注册通知:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(DetailsViewController.keyboardWillShow(_:)), name:UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(DetailsViewController.keyboardWillHide(_:)), name:UIKeyboardWillHideNotification, object: nil)
添加以下观察者方法,当键盘出现时自动滚动。
func textFieldShouldReturn(textField: UITextField) -> Bool
textField.resignFirstResponder()
return true
func keyboardWillShow(notification:NSNotification)
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
keyboardFrame = self.view.convertRect(keyboardFrame, fromView: nil)
var contentInset:UIEdgeInsets = self.scrollView.contentInset
contentInset.bottom = keyboardFrame.size.height
self.scrollView.contentInset = contentInset
func keyboardWillHide(notification:NSNotification)
var contentInset:UIEdgeInsets = UIEdgeInsetsZero
self.scrollView.contentInset = contentInset
【讨论】:
【参考方案23】:我的解决方案有 4 个步骤: - 第1步:当键盘出现时函数监听
- (void)keyboardWasShown:(NSNotification *)notification
// Get the size of the keyboard.
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
//top: 64 for navigation bar, 0 for without navigation
UIEdgeInsets contentInsets = UIEdgeInsetsMake(64, 0, keyboardSize.height, 0);
_scrollView.contentInset = contentInsets;
_scrollView.scrollIndicatorInsets = contentInsets;
- 第 2 步:函数在键盘消失时进行侦听
- (void)keyboardWillHide:(NSNotification *)notification
//top: 64 for navigatiob bar
UIEdgeInsets contentInsets = UIEdgeInsetsMake(64, 0, 0, 0);
[_editScrollView setContentInset: contentInsets];
[_editScrollView setScrollIndicatorInsets: contentInsets];
- 第三步:将这些功能添加到通知中心:
- (void)viewWillAppear:(BOOL)animated
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:) name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
- 第 4 步:当视图控制器消失时移除监听
- (void)viewDidDisappear:(BOOL)animated
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillHideNotification object:nil];
【讨论】:
以上是关于键盘出现时如何滚动 UIScrollView?的主要内容,如果未能解决你的问题,请参考以下文章
键盘消失时,UIScrollView 不会向下滚动到其原始位置