可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm having trouble with my code. I'm trying to move the UIScrollView
when I'm editing an UITextField
that should be hidden by the keyboard pop.
I'm moving the main frame right now because I don't know how to 'scroll up' in the code.
So, I did a little bit of code, it's working fine but when I edit an UItextfield and I switch to another UITextField
without pressing on the 'return' button the main view goes waaayyyyy to far up.
I did an NSLog()
with my variables size, distance and textFieldRect.origin.y as you can see below. When I put two UITextField
at the same place (y origin) and I do this particular 'switch' (wihtout pressing return) , I get the same numbers, whereas my code worked fine for the first UITextField
editing but not for the second editing.
Check this out:
- (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];
}
Any ideas ?
回答1:
The recommended way from Apple is to change the contentInset
of the UIScrollView
. It is a very elegant solution, because you do not have to mess with the contentSize
.
Following code is copied from the Keyboard Programming Guide, where the handling of this issue is explained. You should have a look into it.
// 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 version:
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
}
回答2:
I just implemented this with Swift 2.0 for iOS9 on Xcode 7 (beta 6), works fine here.
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
}
Edited for Swift 3
Seems like you only need to set the contentInset
and scrollIndicatorInset
with Swift 3, the scrolling/contentOffset is done automatically..
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
}
回答3:
All the answers here seem to forget about landscape possibilities. If you would like this to work when the device is rotated to a landscape view, then you will face problems.
The trick here is that although the view is aware of the orientation, the keyboard is not. This means in Landscape, the keyboards width is actually its height and visa versa.
To modify Apples recommended way of changing the content insets and get it support landscape orientation, I would recommend using the following:
// 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;
}
The part to pay attention to here is the following:
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;
}
What is does, is detects what orientation the device is in. If it is landscape, it will 'swap' the width and height values of the keyboardSize variable to ensure that the correct values are being used in each orientation.
回答4:
Swift 4 Solution:
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)
}
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[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
}
@objc func keyboardWillHide(notification: NSNotification) {
scrollView.contentInset = .zero
scrollView.scrollIndicatorInsets = .zero
}
回答5:
For this stuff no need to lot's of coding it's very easy like below code:-
your all textfiled in UIScrollview from nib like this image:-
YourViewController.h
@interface cntrInquiryViewController : UIViewController<UIScrollViewDelegate,UITextFieldDelegate>
{
IBOutlet UITextField *txtName;
IBOutlet UITextField *txtEmail;
IBOutlet UIScrollView *srcScrollView;
}
@end
connect IBOutlet from nib and also Connect each delegate of UItextfiled and scrollview delegate from NIB
-(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;
}
NOTE if Text-filed delegate not connected then no one method working please insure that all iBOulate and delegate connected correctly
回答6:
Apple's recommendation recoded in Swift + Using UIScrollView with Auto Layout in iOS (basing on these following links: 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)
}
}
回答7:
The only thing I would update in Apple code is the keyboardWillBeHidden: method, to provide smooth transition.
// 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;
}
回答8:
Here is a Swift 3 compatible answer, that will also work with view controllers within a navigation controller - as they will change the scroll views contentInset.top
property.
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
}
回答9:
I've found the above answers have been outdated. Also not perfect when scroll.
Here's a swift version.
It will scroll right below the textField, no spare space. And It will restore to the way it was like its first appear.
//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)
}
回答10:
This is what I've been using. It's simple and it works well.
#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);
}];
}
Use the UITextField
delegate call textFieldDidBeginEditing:
to shift your view upwards, and also add a notification observer to return the view to normal when the keyboard hides:
-(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];
}
回答11:
This is the final code with improvements in 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
}
回答12:
Swift 4.2 solution that takes possible heights of UIToolbar and UITabBar into account.
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
}
And, if you're targeting < iOS 9, you have to unregister the observer at some point (thanks Joe)
回答13:
I would do that like this. It's a lot of code but it ensures, that the textField currently in focus is is vertically centered in the 'available space':
- (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;
Note that the - (IBAction)textFieldGotFocus:
action is hooked up to the every textField's DidBeginEditing
state.
Also it would be a little better to get the animation duration from the keyboard notification and use that for the scrollview animation instead of a fixed value, but sue me, this was good enough for me ;)
回答14:
Try this code in 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)
}
回答15:
You actually don't need a UIScrollView to do this. I used this code and it works for me:
-(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){
}];
}
回答16:
You can scroll by using the property contentOffset
in UIScrollView
, e.g.,
CGPoint offset = scrollview.contentOffset;
offset.y -= KEYBOARD_HEIGHT + 5;
scrollview.contentOffset = offset;
There's also a method to do animated scrolling.
As for the reason why your second edit is not scrolling correctly, it could be because you seem to assume that a new keyboard will appear every time editing starts. You could try checking if you've already adjusted for the "keyboard" visible position (and likewise check for keyboard visibility at the moment before reverting it).
A better solution might be to listen for the keyboard notification, e.g.:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
回答17:
I know it's an old question now but i thought it might help others. I wanted something a little easier to implement for a few apps i had, so i made a class for this. You can download it here if you want: https://github.com/sdernley/iOSTextFieldHandler
It's as simple as setting all of the UITextFields to have a delegate of self
textfieldname.delegate = self;
And then adding this to your view controller with the name of your scrollView and submit button
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[iOSTextFieldHandler TextboxKeyboardMover:containingScrollView tf:textField btn:btnSubmit];
}
回答18:
The following is my solutions which works ( 5 steps )
Step1: Add an observer to catch which UITEXTFIELD or UITEXTVIEW ShoudBeginEditing ( where object is inited or ViewDidLoad.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateActiveField:)
name:@"UPDATE_ACTIVE_FIELD" object:nil];
Step2: Post a notification when ..ShouldBeginEditing with OBJECT of UITEXTFIELD or UITEXTVIEW
-(BOOL)textViewShouldBeginEditing:(UITextView *)textView {
[[NSNotificationCenter defaultCenter] postNotificationName:@"UPDATE_ACTIVE_FIELD"
object:textView];
return YES;
}
Step3: The method that (Step1 calles ) assigns the current UITEXTFIELD or UITEXTVIEW
-(void) updateActiveField: (id) sender {
activeField = [sender object];
}
Step4: Add Keyboard observer UIKeyboardWillShowNotification ( same place as Step1 )
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
and method:
// 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: Add Keyboard observer UIKeyboardWillHideNotification ( same place as step 1 )
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
and method:
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
self.layoutPanel.contentInset = _currentEdgeInsets;
self.layoutPanel.scrollIndicatorInsets = _currentEdgeInsets;
self.layoutPanel.scrollEnabled = YES;
}
Remember to remove observers!
回答19:
Use following extension if you don't want to calculate too much:
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)
}
}
And maybe you want keep your UITextField always visible:
func textViewDidChange(textView: UITextView) {
self.scrollView?.scrollSubviewToBeVisible(textView, animated: false)
}
回答20:
I used this answer supplied by Sudheer Palchuri
https://stackoverflow.com/users/2873919/sudheer-palchuri
https://stackoverflow.com/a/32583809/6193496
In ViewDidLoad, register the notifications:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(DetailsViewController.keyboardWillShow(_:)), name:UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(DetailsViewController.keyboardWillHide(_:)), name:UIKeyboardWillHideNotification, object: nil)
Add below observer methods which does the automatic scrolling when keyboard appears.
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
}
回答21:
My solution has 4 step:
- Step 1: the function listens when the keyboard appears
- (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;
}
- Step 2: the function listens when the keyboard disappears
- (void)keyboardWillHide:(NSNotification *)notification {
//top: 64 for navigatiob bar
UIEdgeInsets contentInsets = UIEdgeInsetsMake(64, 0, 0, 0);
[_editScrollView setContentInset: contentInsets];
[_editScrollView setScrollIndicatorInsets: contentInsets];
}
- Step 3: add these functions to the notification center:
- (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];
}
- Step 4: remove listen when view controller disappers
- (void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
回答22:
One of the easiest solutions is using the following protocol:
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 })
}
}
When you want to use this protocol, you only need to conform to it and assign your scroll view in your controller as follows:
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()
}
}