iOS中键盘弹出时将UIView向上移动

74

我有一个UIView,它不在UIScrollView中。当键盘出现时,我想将我的视图上移。之前我尝试使用这个解决方案:如何使UITextField在键盘出现时上移?

它工作得很好。但在填写文本字段数据后,它会带我到另一页,当我回到这个页面时,它只是跳动,并且我无法看到我的文本字段。有没有更好的解决方案来解决这个问题?

18个回答

91
- (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];
}

#pragma mark - keyboard movements
- (void)keyboardWillShow:(NSNotification *)notification
{
    CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    [UIView animateWithDuration:0.3 animations:^{
        CGRect f = self.view.frame;
        f.origin.y = -keyboardSize.height;
        self.view.frame = f;
    }];
}

-(void)keyboardWillHide:(NSNotification *)notification
{
    [UIView animateWithDuration:0.3 animations:^{
        CGRect f = self.view.frame;
        f.origin.y = 0.0f;
        self.view.frame = f;
    }];
}

1
这对我很有效,我结合了这个答案链接,使得当用户在屏幕上的其他地方点击时,键盘会隐藏。 - Richard
3
如果用户切换到高度不同的另一种键盘,比如表情符号键盘,此代码将无效。为了解决这个问题,在 keyboardWillShow 方法中,您应该使用以下代码:CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; - Daniel Albert
2
这个解决方案比苹果提供的要简单得多。苹果的解决方案无法解决我的问题,但是你们的解决方案一下子就搞定了。谢谢。 - TPG
1
这个可以工作。但问题是它也会将顶部视图推到不可见。如何将顶部视图向下移动? - user2872856
1
这是一个非常不错的解决方案,但我建议的是,不要将视图移动那么高,而是先检查文本字段是否被隐藏,然后按照隐藏的量移动它。 - Georgio Sayegh
显示剩余3条评论

80

使用以下代码来显示和隐藏键盘

//Declare a delegate, assign your textField to the delegate and then include these methods

-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
    return YES;
}


- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];

    [self.view endEditing:YES];
    return YES;
}


- (void)keyboardDidShow:(NSNotification *)notification
{
    // Assign new frame to your view 
    [self.view setFrame:CGRectMake(0,-110,320,460)]; //here taken -110 for example i.e. your view will be scrolled to -110. change its value according to your requirement.

}

-(void)keyboardDidHide:(NSNotification *)notification
{
    [self.view setFrame:CGRectMake(0,0,320,460)];
}

48
键盘的大小应该从传递给键盘通知的infoDictionary中确定,并且应该用它来计算适当的yOffset,而不是静态定义。 - isaac
9
这个程序没有正确处理键盘更改通知,这意味着它没有考虑到用户切换键盘语言并导致键盘高度变化的情况(在日语的汉字键盘上按表情符号按钮就可以看到一个例子)。 - Jehiah
感谢 @Ani Shroff 提供的解决方案。上升运动不够平滑,更像是离散的运动。您能帮忙解决吗? - Muhammad Irfan
2
我认为这些通知是不必要的,你可以在textFieldShouldBeginEditing中简单地调用keyboardDidShow,在textFieldShouldEndEditing中调用keyboardDidHide。 - Zar E Ahmer
13
是我还是这种方法存在一个重大问题。每次您开始或结束在文本字段中进行编辑时,都会订阅通知。应该在布局加载时仅订阅一次。 - Nuno Gonçalves
显示剩余6条评论

37

我写了一个小的类别(category)在UIView上,它可以管理暂时滚动一些东西而不需要将整个东西包装成一个UIScrollView。这里使用“scroll”这个动词可能不太理想,因为它可能会让你认为涉及到了一个滚动视图,但实际上并没有—我们只是在动画地移动一个UIView(或者UIView子类)的位置。

这里嵌入了一堆对于我的表单和布局而言适当的魔法数字,这些数字可能不适用于你的情况,所以我鼓励你调整代码以适应你具体的需求。

UIView+FormScroll.h:

#import <Foundation/Foundation.h>

@interface UIView (FormScroll) 

-(void)scrollToY:(float)y;
-(void)scrollToView:(UIView *)view;
-(void)scrollElement:(UIView *)view toPoint:(float)y;

@end

UIView+FormScroll.m:

#import "UIView+FormScroll.h"


@implementation UIView (FormScroll)


-(void)scrollToY:(float)y
{

    [UIView beginAnimations:@"registerScroll" context:NULL];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationDuration:0.4];
    self.transform = CGAffineTransformMakeTranslation(0, y);
    [UIView commitAnimations];

}

-(void)scrollToView:(UIView *)view
{
    CGRect theFrame = view.frame;
    float y = theFrame.origin.y - 15;
    y -= (y/1.7);
    [self scrollToY:-y];
}


-(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];
    }

}

@end

将其导入您的UIViewController中,然后您就可以执行

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    [self.view scrollToView:textField];
}

-(void) textFieldDidEndEditing:(UITextField *)textField
{
    [self.view scrollToY:0];
    [textField resignFirstResponder];
}

…或者其他任何方式。该类别提供了三种相当不错的方法来调整视图的位置。


4
根据传入keyboardWillAppear NSNotification的infoDictionary中的键盘大小,确定传递给该类方法的适当yOffset。 - isaac
2
@isaac - 当然。这只是代码可以改进的几种方式之一。 - Dan Ray

32

将视图绑定到键盘也是一种选择(请参见答案底部的GIF)

Swift 4

使用扩展:(未经完全测试)

extension UIView{
    func bindToKeyboard(){
        NotificationCenter.default.addObserver(self, selector: #selector(UIView.keyboardWillChange(notification:)), name: Notification.Name.UIKeyboardWillChangeFrame, object: nil)
    }

    func unbindToKeyboard(){
        NotificationCenter.default.removeObserver(self, name: Notification.Name.UIKeyboardWillChangeFrame, object: nil)
    }
    @objc
    func keyboardWillChange(notification: Notification) {
        let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
        let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
        let curFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
        let targetFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
        let deltaY = targetFrame.origin.y - curFrame.origin.y

        UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
            self.frame.origin.y+=deltaY

        },completion: nil)

    }
}

Swift 2 + 3

使用扩展(extension):

extension UIView{
    func bindToKeyboard(){
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(UIView.keyboardWillChange(_:)), name: UIKeyboardWillChangeFrameNotification, object: nil)
    }


    func keyboardWillChange(notification: NSNotification) {

        let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
        let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
        let curFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
        let targetFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
        let deltaY = targetFrame.origin.y - curFrame.origin.y


        UIView.animateKeyframesWithDuration(duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
                self.frame.origin.y+=deltaY

        },completion: nil)

    }  
}

使用方法:

// view did load...
textField.bindToKeyboard()

...

// view unload
textField.unbindToKeyboard()

结果:
在此输入图片描述

重要提示
当视图卸载时,请不要忘记移除观察者。


14

我发现theDuncs的答案非常有用,下面是我自己的(重构后)版本:


主要更改

  1. 现在动态获取键盘大小,而不是硬编码数值
  2. 将UIView动画提取为自己的方法,避免重复代码
  3. 允许将持续时间传递给该方法,而不是硬编码

- (void)viewWillAppear:(BOOL)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 {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

- (void)keyboardWillShow:(NSNotification *)notification {
    CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;

    float newVerticalPosition = -keyboardSize.height;

    [self moveFrameToVerticalPosition:newVerticalPosition forDuration:0.3f];
}


- (void)keyboardWillHide:(NSNotification *)notification {
    [self moveFrameToVerticalPosition:0.0f forDuration:0.3f];
}


- (void)moveFrameToVerticalPosition:(float)position forDuration:(float)duration {
    CGRect frame = self.view.frame;
    frame.origin.y = position;

    [UIView animateWithDuration:duration animations:^{
        self.view.frame = frame;
    }];
}

frame.origin.y = frame.origin.y - keyboardSize.height;否则键盘会超出屏幕。 - Alex
1
正是我所需要的,除非你按下“语音输入”按钮,否则视图将永远不会被提升,因为你正在使用UIKeyboardFrameBeginUserInfoKey而不是UIKeyboardFrameEndUserInfoKey。你应该进行编辑,否则完美! - damjandd
我已经修改了我的答案,包括@damjandd的建议 - 谢谢! - Jamie
在更改self.view.frame之后,您还需要[self.view layoutSubviews]。如果@view包含tableTable,则可以解决iPhone X上安全边距的问题。 - Cynichniy Bandera

9

本文基于Daniel Krom的解决方案。这是适用于Swift 3.0的版本。非常适配AutoLayout,当键盘出现时整个视图将被移动。

extension UIView {

    func bindToKeyboard(){
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
    }

    func unbindFromKeyboard(){
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
    }

    @objc
    func keyboardWillChange(notification: NSNotification) {

        guard let userInfo = notification.userInfo else { return }

        let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! Double
        let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as! UInt
        let curFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
        let targetFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
        let deltaY = targetFrame.origin.y - curFrame.origin.y

        UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
            self.frame.origin.y += deltaY
        })
    }  
}

如何使用: 在viewDidLoad中添加bindToKeyboard函数,如下:
override func viewDidLoad() {
    super.viewDidLoad()

    view.bindToKeyboard()
}

在析构函数中添加 unbindFromKeyboard 函数:
deinit {
    view.unbindFromKeyboard()
}

你有这个用 Objective-C 写的吗? - Moxarth
@Moxarth 抱歉,我没有。在Objective-C中,您可以创建类别。这是一个链接,其中包含如何执行此操作的说明:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html - Axel
整个视图上移,保持视图和键盘之间的黑色视图。 - Ankur Lahiry
在 iPhone X 上失败。 - Chatar Veer Suthar

8

Swift 5

Daniel Krom的回答已更新:

extension UIView {

    func bindToKeyboard() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(UIView.keyboardWillChange(notification:)),
            name: UIResponder.keyboardWillChangeFrameNotification,
            object: nil
        )
    }

    func unbindToKeyboard() {
        NotificationCenter.default.removeObserver(
            self,
            name: UIResponder.keyboardWillChangeFrameNotification,
            object: nil
        )
    }

    @objc func keyboardWillChange(notification: Notification) {
        let duration = notification.userInfo![UIResponder.keyboardAnimationDurationUserInfoKey] as! Double
        let curve = notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as! UInt
        let curFrame = (notification.userInfo![UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
        let targetFrame = (notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
        let deltaY = targetFrame.origin.y - curFrame.origin.y

        UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIView.KeyframeAnimationOptions(rawValue: curve), animations: {
            self.frame.origin.y += deltaY
        })
    }

}

5

试试这个:

 [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector (keyboardDidShow:)
                                                 name: UIKeyboardDidShowNotification object:nil];


[[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector (keyboardDidHide:)
                                                 name: UIKeyboardDidHideNotification object:nil];

-(void) keyboardDidShow: (NSNotification *)notif
    {
        CGSize keyboardSize = [[[notif userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
        UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize.height+[self getTableView].tableFooterView.frame.size.height, 0.0);

        [self getTableView].contentInset = contentInsets;
        [self getTableView].scrollIndicatorInsets = contentInsets;

        CGRect rect = self.frame; rect.size.height -= keyboardSize.height;
        if (!CGRectContainsPoint(rect, self.frame.origin))
        {
            CGPoint scrollPoint = CGPointMake(0.0, self.frame.origin.y - (keyboardSize.height - self.frame.size.height));
            [[self getTableView] setContentOffset:scrollPoint animated:YES];
        }
    }

-(void) keyboardDidHide: (NSNotification *)notif
{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    [self getTableView].contentInset = contentInsets;
    [self getTableView].scrollIndicatorInsets = contentInsets;
}

5

3

基于theDunc的答案,但是使用Swift和自动布局。

@IBOutlet weak var bottomConstraint: NSLayoutConstraint! // connect the bottom of the view you want to move to the bottom layout guide

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    NSNotificationCenter.defaultCenter().addObserver(self,
                                                     selector: #selector(ConversationViewController.keyboardWillShow(_:)),
                                                     name: UIKeyboardWillShowNotification,
                                                     object: nil)

    NSNotificationCenter.defaultCenter().addObserver(self,
                                                     selector: #selector(ConversationViewController.keyboardWillHide(_:)),
                                                     name: UIKeyboardWillHideNotification,
                                                     object: nil)
}

override func viewWillDisappear(animated: Bool) {
    NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
    super.viewWillDisappear(animated)
}

// MARK: - Keyboard events

func keyboardWillShow(notification: NSNotification) {
    if let userInfo = notification.userInfo,
        keyboardFrame = userInfo[UIKeyboardFrameBeginUserInfoKey]
    {
        let keyboardSize = keyboardFrame.CGRectValue().size
        self.bottomConstraint.constant = keyboardSize.height
        UIView.animateWithDuration(0.3) {
            self.view.layoutIfNeeded()
        }
    }
}

func keyboardWillHide(notification: NSNotification) {
    self.bottomConstraint.constant = 0
    UIView.animateWithDuration(0.3) {
        self.view.layoutIfNeeded()
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接