-1

I have a pageviewcontroller, which scrolls horizontally, displaying questions and answers.

The contentView for the pageviewcontroller is populated with a scrollview, and inside of this is some stack views, one of which has a textView, for user entry.

enter image description here

When the user taps on the textView, the keyboard pops up, sometimes covering up the textView. When I try to scroll vertically, as I would expect scrollview to allow, the scrollview doesn't respond, I can only scroll horizontally to the next page view.

The problem is that when the keyboard pops up after the user taps in the textview, the textview may be hidden underneath the keyboard, so the user can't see what he's typing. I'd like to have the view scroll upward when he keyboard is tapped, so the user can see what is being typed.

user3284547
  • 105
  • 2
  • 11
  • So to be clear your question is not the question in the title, right? – trndjc Mar 10 '18 at 22:33
  • I was thinking the problem had to do with the vertical scroll view not working with the pageviewcontroller. I didn't realize it was a problem of constraints and the settings of the keyboard. When the keyboard popped up, I tried scrolling up so I could see the textview, but it wouldn't move vertically. – user3284547 Mar 12 '18 at 15:53

3 Answers3

0

That's called "Keyboard managing" You have to:

1) Set observer for keyboard appear and keyboard disappear action

2) Update bottom constraint, when keyboard appears.

3) Update it again, when keyboard disappears.

4) Delete observers, when view will disappear.

Step by step instructions, for example here: Move view with keyboard using Swift

But my recommendation is to update constraints, not origins, as in instruction. Or, maybe, you can set an offset for content

Unreal Developer
  • 485
  • 3
  • 11
  • I followed the instructions for "Move view with keyboard using Swift" and this worked well. Thanks so much. Do you have a simple suggested solution for a "done" button to dismiss the keyboard instead of hitting "return?" I've seen several ways to do this, but they seem a bit complicated. – user3284547 Mar 12 '18 at 16:28
  • The easiest way is keyboard manager framework. For example https://github.com/hackiftekhar/IQKeyboardManager – Unreal Developer Mar 13 '18 at 09:52
  • I'm finding that because I have a stack view, when the keyboard comes up the text field sometimes gets squished smaller and then I can't see what's in it, so I'm curious to know if the solution above would work in coordination with "set and offset for content." Any suggestion on how to do this? – user3284547 Mar 14 '18 at 17:44
  • you have to push up the bottom constraint of scroll view. all content will be pushed too – Unreal Developer Mar 14 '18 at 17:59
0

you should set an bottom constraint into your stack view and then controll drag that in your class like this :

class yourClassViewController: UIViewController {
// MARK: Properties
@IBOutlet weak var bottomConstraint: NSLayoutConstraint!

then in your viewDidLoad method write like this:

 override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(self.keyboardNotification(notification:)),
                                           name: NSNotification.Name.UIKeyboardWillChangeFrame,
                                           object: nil)


    // Do any additional setup after loading the view.
}

and below that:

   deinit {
    NotificationCenter.default.removeObserver(self)
}

@objc func keyboardNotification(notification: NSNotification) {
    if let userInfo = notification.userInfo {
        let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
        let endFrameY = endFrame?.origin.y ?? 0
        let duration:TimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
        let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
        let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions.curveEaseInOut.rawValue
        let animationCurve:UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
        if endFrameY >= UIScreen.main.bounds.size.height {
            self.bottomConstraint?.constant = 0.0
        } else {
            self.bottomConstraint?.constant = endFrame?.size.height ?? 0.0
        }
        UIView.animate(withDuration: duration,
                       delay: TimeInterval(0),
                       options: animationCurve,
                       animations: { self.view.layoutIfNeeded() },
                       completion: nil)
    }
}

for disappear keyboard in touchsBegan method you should write like this:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
          self.view.endEditing(true)
}
msinamsh
  • 36
  • 2
0

Use notification observers to handle keyboard show and hide events:

// keyboard will show
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowAction(_:)), name: .UIKeyboardWillShow, object: nil)

// keyboard will hide
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHideAction(_:)), name: .UIKeyboardWillHide, object: nil)

You can do something like this when the keyboard is about to appear:

// keyboard will show action
@objc private func keyboardWillShowAction(_ notification: NSNotification) {

    currentScrollViewOffset = scrollView.contentOffset

    let keyboardFrame = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue
    let keyboardHeight = keyboardFrame?.cgRectValue.height
    let padding: CGFloat = 32

    // if text field view would be obstructed by keyboard, apply minimum-needed scroll view offset
    if activeTextFieldMaxYOnScreen! > (UIScreen.main.bounds.height - keyboardHeight! - padding) {

        let temporaryOffset = activeTextFieldMaxYOnScreen! - (UIScreen.main.bounds.height - (keyboardHeight! + padding))
        scrollView.setContentOffset(CGPoint(x: 0, y: currentScrollViewOffset.y + temporaryOffset), animated: true)

    }

    scrollView.addGestureRecognizer(tapToDismissKeyboard)
    scrollView.isScrollEnabled = false

}

And then revert when the keyboard is dismissed:

// keyboard will hide action
@objc private func keyboardWillHideAction(_ notification: NSNotification) {

    scrollView.setContentOffset(currentScrollViewOffset, animated: true)
    scrollView.removeGestureRecognizer(tapToDismissKeyboard)
    scrollView.isScrollEnabled = true

}

It's also good practice to remove observers in the deinit method:

NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil)

Note that currentScrollViewOffset and activeTextFieldMaxYOnScreen are instance properties of the view controller. To get the activeTextFieldMaxYOnScreen value, you can get it from the text field delegate:

// text field should begin editing
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {

    // convert text field line's max-y to perceived max-y on the screen
    let line = textField.viewWithTag(123)
    activeTextFieldMaxYOnScreen = (line?.convert((line?.bounds.origin)!, to: nil).y)! + (line?.frame.height)!
    return true

}
trndjc
  • 11,654
  • 3
  • 38
  • 51