UITextField: move view when keyboard appears

后端 未结 7 1268
心在旅途
心在旅途 2020-11-29 16:32

I\'m currently working on an iPhone application with a single view, which has multiple UITextFields for input. When the keyboard shows, it overlays the bottom textfields. So

相关标签:
7条回答
  • 2020-11-29 16:59

    Fairly easy solution, works with all screen sizes

    First you have to embed you UITextFields to a UIScrollView. In my case, I had several UITextFields and a UITextView.

    Then you have to inherit from UITextFieldDelegate, UITextViewDelegate.

    class SettingsVC: UIViewController, UITextFieldDelegate, UITextViewDelegate

    Assign textfield's and textview's delegates to self.

    fullNameTextField.delegate = self usernameTextField.delegate = self websiteTextField.delegate = self profileDescription.delegate = self

    Then use this code:

    var editingTextInput: UIView!
    
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(self.keyboardShown(notification:)),
                                               name: NSNotification.Name.UIKeyboardDidShow,
                                               object: nil)
        
    
      }
      
        override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardDidShow, object: nil)
      }
      
      func keyboardShown(notification: NSNotification) {
        
        if let infoKey  = notification.userInfo?[UIKeyboardFrameEndUserInfoKey],
          let rawFrame = (infoKey as AnyObject).cgRectValue {
          
          let keyboardFrame = view.convert(rawFrame, to: view)
          let editingTextInputFrame = self.editingTextInput.convert(self.editingTextInput.frame, to: view)
    
          
          if editingTextInputFrame.maxY > keyboardFrame.minY{
            
            let diff = keyboardFrame.minY - editingTextInputFrame.maxY
            containerScrollView.setContentOffset(CGPoint(x: 0, y: -diff), animated: true)
            
          }
        }
      }
      
      func textFieldDidBeginEditing(_ textField: UITextField) {
        self.editingTextInput = textField
      }
      
      func textViewDidBeginEditing(_ textView: UITextView) {
        self.editingTextInput = textView
      }
      
      func textFieldDidEndEditing(_ textField: UITextField) {
        containerScrollView.setContentOffset(CGPoint.zero, animated: true)
      }
      
      func textViewDidEndEditing(_ textView: UITextView) {
        containerScrollView.setContentOffset(CGPoint.zero, animated: true)
      }

    In short, you subscribe to UIKeyboardDidShow notification. When you tap on textField or textView keyboard is shown and you grab keyboard's frame and frame of input element you have tapped on. Convert them to viewController's coordinate system and compare input element's lowest point to a keyboard's highest. If element's lower part is lower than keyboard's highest, than set offset of containerScrollView to the difference between them.

    if editingTextInputFrame.maxY > keyboardFrame.minY{
                
                let diff = keyboardFrame.minY - editingTextInputFrame.maxY
                containerScrollView.setContentOffset(CGPoint(x: 0, y: -diff), animated: true)
                
              }

    0 讨论(0)
  • 2020-11-29 17:02

    I just solved this problem. The solution is a combination of a UIKeyboardDidShowNotification and UIKeyboardDidHideNotification observer with the above textFieldDidBeginEditing: and textFieldDidEndEditing: methods.

    You need three additional variables, one to store the current selected UITextField (which I have named activeField), one to indicate if the current view has been moved, and one to indicate if the keyboard is displayed.

    This is how the two UITextField delegate methods look now:

    - (void)textFieldDidBeginEditing:(UITextField *)textField {
        activeField = textField;
    }
    
    - (void)textFieldDidEndEditing:(UITextField *)textField {
        activeField = nil;
        // Additional Code
    }
    

    When the view is loaded, the following two observers are created:

    - (void)viewDidLoad {
        // Additional Code
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(keyboardWasShown:)
                                                     name:UIKeyboardDidShowNotification
                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(keyboardWasHidden:)
                                                     name:UIKeyboardDidHideNotification
                                                   object:nil];
    }
    

    And the corresponding methods are implemented as follows:

    - (void)keyboardWasShown:(NSNotification *)aNotification {
        if ( keyboardShown )
            return;
    
        if ( ( activeField != inputAmount ) && ( activeField != inputAge ) ) {
            NSDictionary *info = [aNotification userInfo];
            NSValue *aValue = [info objectForKey:UIKeyboardBoundsUserInfoKey];
            CGSize keyboardSize = [aValue CGRectValue].size;
    
            NSTimeInterval animationDuration = 0.300000011920929;
            CGRect frame = self.view.frame;
            frame.origin.y -= keyboardSize.height-44;
            frame.size.height += keyboardSize.height-44;
            [UIView beginAnimations:@"ResizeForKeyboard" context:nil];
            [UIView setAnimationDuration:animationDuration];
            self.view.frame = frame;
            [UIView commitAnimations];
    
            viewMoved = YES;
        }
    
        keyboardShown = YES;
    }
    
    - (void)keyboardWasHidden:(NSNotification *)aNotification {
        if ( viewMoved ) {
            NSDictionary *info = [aNotification userInfo];
            NSValue *aValue = [info objectForKey:UIKeyboardBoundsUserInfoKey];
            CGSize keyboardSize = [aValue CGRectValue].size;
    
            NSTimeInterval animationDuration = 0.300000011920929;
            CGRect frame = self.view.frame;
            frame.origin.y += keyboardSize.height-44;
            frame.size.height -= keyboardSize.height-44;
            [UIView beginAnimations:@"ResizeForKeyboard" context:nil];
            [UIView setAnimationDuration:animationDuration];
            self.view.frame = frame;
            [UIView commitAnimations];
    
            viewMoved = NO;
        }
    
        keyboardShown = NO;
    }
    

    This code works now as expected. The keyboard is only dismissed when the Done button is pressed, otherwise it stays visible and the view is not moved around.

    As an additional note, I think it is possible to get the animationDuration dynamically by asking the NSNotification object, since I have already played with a similar solution but didn't get it to work (which it does now).

    0 讨论(0)
  • 2020-11-29 17:04

    Like I mentioned in this answer:

    I've developed a framework for my own need to solve this issue better, and made it public now. It's not just for UITextField and UITextView, It works for any custom UIView that adopts UITextInput protocol like UITextField and UITextView and offers many useful features. You can install it via Carthage, CocoaPods or Swift Package Manager.

    ODScrollView GitHub

    ODScrollView Medium

    ODScrollView is just a UIScrollView that automatically moves editable text areas like UITextField and UITextView vertically depending on keyboard visibility to offer better user experience.

    Features

    • Automatically moves first responder UIViews that adopt the UITextInput protocol up/down when the keyboard appears/disappears, e.g., UITextField, UITextView, UISearchTextField or any custom UIView that adopts UITextInput protocol.
      • Note that if UITextInput's frame does NOT fits the remaining area between ODScrollView and keyboard, then ODScrollView adjusts UITextInput based on cursor position instead of frame. In such cases, "trackTextInputCursor" feature can be used. Example
    • Adjustment margin can be applied for each UITextInput seperately for .Top and .Bottom adjustment direction setting. 20 CGFloat by default.

    • Adjustment can be enabled/disabled for each UITextInput seperately. true by default.

    • Adjustment directon - .Top, .Center, .Bottom - can be applied for each UITextInput seperately. .Bottom by default. Example

    • Adjustment options determines how ODScrollView adjusts. .Always by default.
      • .Always : ODScrollView always adjusts the UITextInput which is placed anywhere in the ODScrollView regardless UITextInput overlaps or not with shown keyboard. Example
      • .IfNeeded : ODScrollView only adjusts the UITextInput if it overlaps with the shown keyboard. Example
    • Besides UIScrollView.keyboardDismissModes, the keyboard can be dismissed by tapping a UIView which is provided by ODScrollViewDelegate. After the keyboard is dismissed, ODScrollView can return its original position. nil and false by default. Example

    Usage

    1 - First thing you need to do is setting up ODScrollView and its content view properly. Since ODScrollView is just a UIScrollView, you can implement ODScrollView same way you do for UIScrollView. It's up to you to create ODScrollView by using storyboard or programmatically.

    If you're creating ODScrollView programmatically, you can continue from step 4.

    Suggested way to create UIScrollView in Storyboard

    - If you are using Content Layout Guide and Frame Layout Guide:
        1.1 - scrollView: Place UIScrollView anywhere you want to use.  
        1.2 - contentView: Place UIView inside scrollView.
        1.3 - Set contentView's top, bottom, leading and trailing constraints to Content Layout Guide's constraints.
        1.4 - Set contentView's width equal to Frame Layout Guide's width.
        1.5 - Set contentView's height equal to Frame Layout Guide's height or set static height which is larger than scrollView's height.
        1.6 - Build your UI inside contentView.
    
    - If you are NOT using Content Layout Guide and Frame Layout Guide:
        1.1 - scrollView: Place UIScrollView anywhere you want to use.  
        1.2 - contentView: Place UIView inside scrollView.
        1.3 - Set contentView's top, bottom, leading and trailing constraints to 0.
        1.4 - Set contentView's width equal to scrollView's width.
        1.5 - Set contentView's height equal to scrollView's superview's height or set static height which is larger than scrollView's height.
        1.6 - Build your UI inside contentView.
    

    2 - Change the scrollView's class from UIScrollView to ODScrollView in the identity inspector on Storyboard.

    3 - Create IBOutlets for scrollView and contentView on ViewController.

    4 - Call the following methods inside ViewDidLoad() on ViewController:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        //ODScrollView setup
        scrollView.registerContentView(contentView)
        scrollView.odScrollViewDelegate = self
    }  
    

    5 - Optional: You still can use UIScrollView's features:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        //ODScrollView setup
        scrollView.registerContentView(contentView)
        scrollView.odScrollViewDelegate = self
    
        // UIScrollView setup
        scrollView.delegate = self // UIScrollView Delegate
        scrollView.keyboardDismissMode = .onDrag // UIScrollView keyboardDismissMode. Default is .none.
    
        UITextView_inside_contentView.delegate = self
    }
    

    6 - Adopt ODScrollViewDelegate from ViewController and decide ODScrollView options:

    extension ViewController: ODScrollViewDelegate {
    
        // MARK:- State Notifiers: are responsible for notifiying ViewController about what is going on while adjusting. You don't have to do anything if you don't need them.
    
        // #Optional
        // Notifies when the keyboard showed.
        func keyboardDidShow(by scrollView: ODScrollView) {}
    
        // #Optional
        // Notifies before the UIScrollView adjustment.
        func scrollAdjustmentWillBegin(by scrollView: ODScrollView) {}
    
        // #Optional
        // Notifies after the UIScrollView adjustment.
        func scrollAdjustmentDidEnd(by scrollView: ODScrollView) {}
    
        // #Optional
        // Notifies when the keyboard hid.
        func keyboardDidHide(by scrollView: ODScrollView) {}
    
        // MARK:- Adjustment Settings
    
        // #Optional
        // Specifies the margin between UITextInput and ODScrollView's top or bottom constraint depending on AdjustmentDirection
        func adjustmentMargin(for textInput: UITextInput, inside scrollView: ODScrollView) -> CGFloat {
            if let textField = textInput as? UITextField, textField == self.UITextField_inside_contentView {
                return 20
            } else {
                return 40
            }
        }
    
        // #Optional
        // Specifies that whether adjustment is enabled or not for each UITextInput seperately.
        func adjustmentEnabled(for textInput: UITextInput, inside scrollView: ODScrollView) -> Bool {
            if let textField = textInput as? UITextField, textField == self.UITextField_inside_contentView {
                return true
            } else {
                return false
            }
        }
    
    
        // Specifies adjustment direction for each UITextInput. It means that  some of UITextInputs inside ODScrollView can be adjusted to the bottom, while others can be adjusted to center or top.
        func adjustmentDirection(selected textInput: UITextInput, inside scrollView: ODScrollView) -> AdjustmentDirection {
            if let textField = textInput as? UITextField, textField == self.UITextField_inside_contentView {
                return .bottom
            } else {
                return .center
            }
        }
    
        /**
         - Always : ODScrollView always adjusts the UITextInput which is placed anywhere in the ODScrollView.
         - IfNeeded : ODScrollView only adjusts the UITextInput if it overlaps with the shown keyboard.
         */
        func adjustmentOption(for scrollView: ODScrollView) -> AdjustmentOption {
            .Always
        }
    
        // MARK: - Hiding Keyboard Settings
    
        /**
         #Optional
    
         Provides a view for tap gesture that hides keyboard.
    
         By default, keyboard can be dismissed by keyboardDismissMode of UIScrollView.
    
         keyboardDismissMode = .none
         keyboardDismissMode = .onDrag
         keyboardDismissMode = .interactive
    
         Beside above settings:
    
         - Returning UIView from this, lets you to hide the keyboard by tapping the UIView you provide, and also be able to use isResettingAdjustmentEnabled(for scrollView: ODScrollView) setting.
    
         - If you return nil instead of UIView object, It means that hiding the keyboard by tapping is disabled.
         */
        func hideKeyboardByTappingToView(for scrollView: ODScrollView) -> UIView? {
            self.view
        }
    
        /**
         #Optional
    
         Resets the scroll view offset - which is adjusted before - to beginning its position after keyboard hid by tapping to the provided UIView via hideKeyboardByTappingToView.
    
         ## IMPORTANT:
         This feature requires a UIView that is provided by hideKeyboardByTappingToView().
         */
        func isResettingAdjustmentEnabled(for scrollView: ODScrollView) -> Bool {
            true
        }
    }
    

    7 - Optional: You can adjust the ODScrollView when the cursor overlaps with keyboard while typing in multiline UITextInput. trackTextInputCursor(for UITextInput) must be called by UITextInput functions that is fired while typing.

    /**
    ## IMPORTANT:
    This feature is not going to work unless textView is subView of _ODScrollView
    */
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
           _ODScrollView.trackTextInputCursor(for textView)
       return true
    }
    
    0 讨论(0)
  • This solution is based on ComSubVie's one.

    Advantages:

    • It supports device rotation - works for all orientations;
    • It doesn't hardcode the values for animation duration and curve, it reads them from the keyboard notification;
    • It utilizes UIKeyboardWillShowNotification instead of UIKeyboardDidShowNotification to sync keyboard animation and custom actions;
    • It doesn't use the deprecated UIKeyboardBoundsUserInfoKey;
    • It handles keyboard resize due to pressing the International key;
    • Fixed memory leak by unregistering for keyboard events;
    • All keyboard handling code is encapsulated in a separate class - KBKeyboardHandler;
    • Flexibility - KBKeyboardHandler class may be easy extended / modified to better suit specific needs;

    Limitations:

    • Works for iOS 4 and above, it needs small modifications to support older versions;
    • It works for applications with a single UIWindow. If you use multiple UIWindows, you may need to modify retrieveFrameFromNotification: method.

    Usage:

    Include KBKeyboardHandler.h, KBKeyboardHandler.m and KBKeyboardHandlerDelegate.h in your project. Implement the KBKeyboardHandlerDelegate protocol in your view controller - it consists of a single method, which will be called when keyboard is shown, hidden or its size is changed. Instantiate the KBKeyboardHandler and set its delegate (typically self). See sample MyViewController below.

    KBKeyboardHandler.h:

    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    @protocol KBKeyboardHandlerDelegate;
    
    @interface KBKeyboardHandler : NSObject
    
    - (id)init;
    
    // Put 'weak' instead of 'assign' if you use ARC
    @property(nonatomic, assign) id<KBKeyboardHandlerDelegate> delegate; 
    @property(nonatomic) CGRect frame;
    
    @end
    

    KBKeyboardHandler.m:

    #import "KBKeyboardHandler.h"
    #import "KBKeyboardHandlerDelegate.h"
    
    @implementation KBKeyboardHandler
    
    - (id)init
    {
        self = [super init];
        if (self)
        {
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                     selector:@selector(keyboardWillShow:)
                                                         name:UIKeyboardWillShowNotification
                                                       object:nil];
    
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                     selector:@selector(keyboardWillHide:)
                                                         name:UIKeyboardWillHideNotification
                                                       object:nil];
        }
    
        return self;
    }
    
    - (void)dealloc
    {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
        [super dealloc];
    }
    
    @synthesize delegate;
    @synthesize frame;
    
    - (void)keyboardWillShow:(NSNotification *)notification
    {
        CGRect oldFrame = self.frame;    
        [self retrieveFrameFromNotification:notification];
    
        if (oldFrame.size.height != self.frame.size.height)
        {
            CGSize delta = CGSizeMake(self.frame.size.width - oldFrame.size.width,
                                      self.frame.size.height - oldFrame.size.height);
            if (self.delegate)
                [self notifySizeChanged:delta notification:notification];
        }
    }
    
    - (void)keyboardWillHide:(NSNotification *)notification
    {
        if (self.frame.size.height > 0.0)
        {
            [self retrieveFrameFromNotification:notification];
            CGSize delta = CGSizeMake(-self.frame.size.width, -self.frame.size.height);
    
            if (self.delegate)
                [self notifySizeChanged:delta notification:notification];
        }
    
        self.frame = CGRectZero;
    }
    
    - (void)retrieveFrameFromNotification:(NSNotification *)notification
    {
        CGRect keyboardRect;
        [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardRect];
        self.frame = [[UIApplication sharedApplication].keyWindow.rootViewController.view convertRect:keyboardRect fromView:nil];
    }
    
    - (void)notifySizeChanged:(CGSize)delta notification:(NSNotification *)notification
    {
        NSDictionary *info = [notification userInfo];
    
        UIViewAnimationOptions curve;
        [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&curve];
    
        NSTimeInterval duration;
        [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&duration];
    
        void (^action)(void) = ^{
            [self.delegate keyboardSizeChanged:delta];
        };
    
        [UIView animateWithDuration:duration
                              delay:0.0
                            options:curve
                         animations:action
                         completion:nil];    
    }
    
    @end
    

    KBKeyboardHandlerDelegate.h:

    @protocol KBKeyboardHandlerDelegate
    
    - (void)keyboardSizeChanged:(CGSize)delta;
    
    @end
    

    Sample MyViewController.h:

    @interface MyViewController : UIViewController<KBKeyboardHandlerDelegate>
    ...
    @end
    

    Sample MyViewController.m:

    @implementation MyViewController
    {
        KBKeyboardHandler *keyboard;
    }
    
    - (void)dealloc
    {
        keyboard.delegate = nil;
        [keyboard release];
        [super dealloc];
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        keyboard = [[KBKeyboardHandler alloc] init];
        keyboard.delegate = self;
    }
    
    - (void)viewDidUnload
    {
        [super viewDidUnload];
        keyboard.delegate = nil;
        [keyboard release];
        keyboard = nil;
    }
    
    - (void)keyboardSizeChanged:(CGSize)delta
    {
        // Resize / reposition your views here. All actions performed here 
        // will appear animated.
        // delta is the difference between the previous size of the keyboard 
        // and the new one.
        // For instance when the keyboard is shown, 
        // delta may has width=768, height=264,
        // when the keyboard is hidden: width=-768, height=-264.
        // Use keyboard.frame.size to get the real keyboard size.
    
        // Sample:
        CGRect frame = self.view.frame;
        frame.size.height -= delta.height;
        self.view.frame = frame;
    }
    

    UPDATE: Fixed iOS 7 warning, thanks @weienv.

    0 讨论(0)
  • This view controller must be UITextView Delegate and you must set self.textview.delegate = self in viewdidload

     -(void) textViewDidBeginEditing:(UITextView *)textView
        {
            NSLog(@"%f",self.view.frame.origin.y);
            [UIView beginAnimations:nil context:NULL];
            [UIView setAnimationDuration:0.25f];
            CGRect frame = self.view.frame;
            frame.origin.y =frame.origin.y -204;
            [self.view setFrame:frame];
            [UIView commitAnimations];
        }
    
    -(void) textViewDidEndEditing:(UITextView *)textView
    {
        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:0.25f];
        CGRect frame = self.view.frame;
        frame.origin.y = frame.origin.y + 204;
        [self.view setFrame:frame];
        [UIView commitAnimations];
    }
    
    0 讨论(0)
  • 2020-11-29 17:13
    Write below code in your view controller. tbl is your table view. 
    
    -(void)viewWillAppear:(BOOL)animated{
    
    
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
        [[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:UIKeyboardWillChangeFrameNotification object:nil];
        [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
    }
    
    
    
    #pragma mark - Keyboard Methods
    -(void)keyboardWillShow:(NSNotification *)notification
    {
    //    375 × 667  ( 750 × 1334 ) iPhone 6
        //414 × 736
        CGRect keyboardRect = [[[notification userInfo] valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
        int H = [[UIScreen mainScreen] bounds].size.height - 64- 20 -keyboardRect.size.height;
        [UIView animateWithDuration:0.5 animations:^{
            tbl.contentInset = UIEdgeInsetsMake(tbl.contentInset.top, tbl.contentInset.left, H, tbl.contentInset.right);
        }];
    }
    
    -(void)keyboardWillChangeFrame:(NSNotification *)notification
    {
        CGRect keyboardRect = [[[notification userInfo] valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    //    int H = IS_IPHONE_5?504-keyboardRect.size.height:416-keyboardRect.size.height;
         int H = [[UIScreen mainScreen] bounds].size.height - 64- 20  -keyboardRect.size.height;
        [UIView animateWithDuration:0.5 animations:^{
            //        scroll.frame = rect;
            tbl.contentInset = UIEdgeInsetsMake(tbl.contentInset.top, tbl.contentInset.left, H, tbl.contentInset.right);
        }];
    }
    
    
    -(void)keyboardWillHide:(NSNotification *)notification
    {
    
        [UIView animateWithDuration:0.3 animations:^{
            //        scroll.frame = rect;
            tbl.contentInset = UIEdgeInsetsMake(tbl.contentInset.top, tbl.contentInset.left, 0, tbl.contentInset.right);
        }];
    }
    
    0 讨论(0)
提交回复
热议问题