What is the correct way to show and hide keyboard without blocking the view?

Hi all, as per title:


I have 2 textfields they happened to belong in the bottom half of the View. Thus, the keyboard will block the user's view when the textfield is pressed.


The app is target at iOS 8 and above.


Here's my attempt:


override func viewDidLoad() {
        super.viewDidLoad()
//To auto hide keyboard when surrounding is pressed
        let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "dismissKeyboard")
        self.view!.addGestureRecognizer(tap)

//Add Notification whenever Keyboard Shows
        NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: nil);
    
        //Add Notification whenever Keyboard Hides
        NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: nil);

//Notification when User goes to home screen or lock screen
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "willResignActive:", name: UIApplicationWillResignActiveNotification, object: nil)
}


    func dismissKeyboard() {
        //Hides keyboard
        view.endEditing(true)
    }

    func willResignActive(notification: NSNotification) {
        //Will Hides Keyboard once user goes to Home Screen / Lock Screen
       dismissKeyboard()
    }


    func animateTextField(up: Bool){
        let movementDistance: CGFloat = 100
        let movementDuration: Double = 0.3
        let movement: CGFloat = (up ? -movementDistance : movementDistance)
        UIView.beginAnimations("anim", context: nil)
        UIView.setAnimationBeginsFromCurrentState(true)
        UIView.setAnimationDuration(movementDuration)
        self.view.frame = CGRectOffset(self.view.frame, 0, movement)
        UIView.commitAnimations()
    }

    func keyboardWillShow(sender: NSNotification) {
        animateTextField(true)    
    }
    func keyboardWillHide(sender: NSNotification) {
           animateTextField(false)
    }



Here's the problem:


1) Folowing warning message is shown for 3 times (Load and during tap on 2 textfields so as to show keyboard) only Personal Hotspot is connected to another device (Probably, Because of the Blue bar on top?). Therefore, I believe that the same would apply whenever there are bars on top (While receving calls which is a green bar or possibly widget notification that shows on top only)


Unable to simultaneously satisfy constraints.
  Probably at least one of the constraints in the following list is one you don't want. 
  Try this: 
  (1) look at each constraint and try to figure out which you don't expect; 
  (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x1356eda10 V:|-(20)-[UIInputSetContainerView:0x1356e3d80]   (Names: '|':UITextEffectsWindow:0x1356ea3b0 )>",
    "<NSLayoutConstraint:0x1357c2840 'UIInputWindowController-top' V:|-(0)-[UIInputSetContainerView:0x1356e3d80]   (Names: '|':UITextEffectsWindow:0x1356ea3b0 )>"
)
Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x1356eda10 V:|-(20)-[UIInputSetContainerView:0x1356e3d80]   (Names: '|':UITextEffectsWindow:0x1356ea3b0 )>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.



2) The following code, I know is a constant. So it may not work well for all devices (I am using iPhone 5S running iOS 9.2.1) as well.

let movementDistance: CGFloat = 100



I choose not to use a scroll view because the page was set to be exactly fit into one page.


Any suggestions besides ScrollView first?

You should be moving the entire view not the textfield.

Ah, could u pinpoint more specific where did I moved the textfield instead of entire view given that I saw this code:


        self.view.frame = CGRectOffset(self.view.frame, 0, movement)


Thanks a lot.

Perhaps I misread your code.

You're assuming an offset of 0 which would not be true if any bars were above your view.

You need to grab the actual layout before either moving the view up or restoring it down.

Accepted Answer

I believe using a scroll view is the best solution. Then you can just set the scroll view's content inset to account for the keyboard - convert keyboard frame to your view's coordinate system, calculate intersection with your bounds, set insets, call scrollRectToVisible for the current first responder text field and you're done. You can disable user scrolling on the scroll view so the user doesn't know it's there. Your code automatically works on multiple screen sizes, portrait or landscape, with any text field no matter where you put it. (Assuming you add some code in your did begin / end editing delegate methods to keep track of the current first responder.)


That constraint warning with the double height status bar is another story I think. Based on other threads on the subject it sounds like a UIKit bug. You can search these forums for that specific error message for more details / background.

Thanks for the suggestion. I think if it is a bug, there is nothing I could do for now. Hence, I will mark your answer as correct.

Yup, thanks for the suggestion. I found out that


self.view.frame.origin.y



Returns 20 when a Personal Network is sharing with at least 1 device (Hence the Blue bar is shown)


So this is what I did instead:


  func animateTextField(notification: NSNotification, up: Bool){
        let keyboardSize = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue().size
       
          //toMove is a global variable that checks if the keyboard has shown. This is to avoid ViewController to raise 2 times when User taps on TextFieldA and followed by tapping on TextfieldB
          if (toMove != false){
            let movementDistance: CGFloat = keyboardSize.height - InitialYScreen
            //InitialXScreen and InitialYScreen are self.view.frame.origin.x and self.view.frame.origin.y respectively during viewDidLoad()
            let movementDuration: Double = 0.3
          
            let movement: CGFloat = (up ? -movementDistance : movementDistance)
            UIView.beginAnimations("anim", context: nil)
            UIView.setAnimationBeginsFromCurrentState(true)
            UIView.setAnimationDuration(movementDuration)
            self.view.frame = CGRectOffset(self.view.frame, InitialXScreen, movement)
            UIView.commitAnimations()
        }
     
    }

    /
    func keyboardWillShow(sender: NSNotification) {
      
        animateTextField(sender, up: true)
        toMove = false
    }


  
    func keyboardWillHide(sender: NSNotification) {
        toMove = true
        animateTextField(sender, up:false)
    }
What is the correct way to show and hide keyboard without blocking the view?
 
 
Q