0

I'm trying to create a page in an app that's your standard style messaging screen. I'm having trouble getting everything to position correctly when the keyboard slides into view. I'll post screenshots (sadly not inline), but here is my structure:

VIEWCONTROLLER
|-View
  |-Scroll View
    |-Content View
      |-TextField
      |-TableView (messages)

Everything is showing up as I would like it to when first loaded: If there aren't enough messages to fill the screen, the messages start at the top followed by a gap, and the text field is pinned to the bottom. Nothing scrolls. If there are a lot of messages, I am successfully scrolling the table to the last row and the textfield is pinned to the bottom of the screen still.

When the textfield is activated however, and there aren't a lot of messages, the gap between the table and the textfield remains and the messages are pushed out of view to the top.

I am trying to get the gap to shrink so the messages stay. This is standard in other messaging apps, but I cannot figure out how to do it

Initial view

Textfield activated, keyboard appears

Scrolling to display messages hides the textfield

UI Layout and constraints

Lastly, here is the code I have for keyboardWillShow. You'll notice some comments of things I have tried unsuccessfully.

func keyboardWillShow(notification:NSNotification) {  
    var userInfo = notification.userInfo!  
    let keyboardFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size  
    let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardFrame!.height, 0.0)  

    self.scrollView.contentInset = contentInsets
    self.scrollView.scrollIndicatorInsets = contentInsets

    // scrollViewBottomConstraint.constant = keyboardFrame!.height - bottomLayoutGuide.length
    // contentViewHeightConstraint.constant = -keyboardFrame!.height
    // self.notificationReplyTable.frame.size.height -= keyboardFrame!.height


    var aRect: CGRect = self.view.frame
    aRect.size.height -= keyboardFrame!.height

    if let activeField = self.activeField {
        if(!aRect.contains(activeField.frame.origin)) {
            self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
        }
    }
}

I feel like the piece I'm missing is pretty small, but just don't know enough Swift 3 to nail this. Thank you for your help!

Edit: the problem is similar to this question with no accepted answer.

Shivam Tripathi
  • 1,405
  • 3
  • 19
  • 37
Philip
  • 45
  • 6

1 Answers1

0

A way to this is to set up vertical autolayout constraints like this (but you will need a reference to the actual bottomMargin constraint to be able to modify it) :

"V:|[scrollView][textField]-(bottomMargin)-|"

The first time you arrive on the screen, bottomMargin is set to 0.

Then when keyboardWillShow is called, get the keyboard frame (cf How to get height of Keyboard?)

func keyboardWillShow(_ notification: Notification) {
    if let keyboardFrame: NSValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
        let keyboardRectangle = keyboardFrame.cgRectValue
        let keyboardHeight = keyboardRectangle.height
    }
}

And animate the constraint bottomMargin to get the height of the keyboard (the duration is 0.3 after some tests, but you can adjust it) :

bottomConstraint.constant = keyboardHeight
UIView.animate(withDuration: 0.3, delay: 0, options: nil, animations: {
  self.view.layoutIfNeeded()
}

That means that every time the keyboard will appear, an animation will move up the text field, hence the scroll view height will be smaller and everything will fit in the screen.

!! Don't forget to test it on landscape mode if you support it, and on iPad too!!

Finally, handle the case when the keyboard will disappear in the keyboardWillHide and set bottomMargin back to 0 :

func keyboardWillHide(_ notification: Notification) {
    bottomConstraint.constant = 0
    UIView.animate(withDuration: 0.3, delay: 0, options: nil, animations: {
      self.view.layoutIfNeeded()
    }
}
  • Could you clarify what you mean by `"V:|[scrollView][textField]-(bottomMargin)-|"` please? – Philip Feb 05 '18 at 10:28
  • That's autolayout visual format, that means that the `scrollView` top should be aligned to the `superview` top, and that `scrollView` bottom should be aligned to `textField` top, and finally that `textField` bottom should at `bottomMargin` pixel from `superview` bottom. –  Feb 05 '18 at 13:33
  • I tried this, but it required moving the UITextField to be outside of the UIScrollView. Doing this seems to make the "swipe to dismiss keyboard" not work consistently or well. Also at that point the only thing really in the ScrollView is the TableView so why bother having the ScrollView? I tried simplifying and only having a TextField and sibling TableView, but that didn't work properly either. – Philip Feb 06 '18 at 22:40