1

Solution which worked for me

After struggling for days I finally manage to find the fix for view animation issue. For me keyboardWillChangeFrameNotification worked. The solution is discussed in the following thread:

Move textfield when keyboard appears swift

Gif

I've a bunch of views embedded inside stack view along with text view. I've given text view height of <= 120. In keyboardWillShow view doesn't animate despite adding code as required. I've played with duration value but it's all same result. I was wondering if it's due to text view? How to fix it?

@objc func keyboardWillShow(notification:NSNotification) {

    guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }

    let keyboardScreenEndFrame = keyboardValue.cgRectValue
    let keyboardFrame = view.convert(keyboardScreenEndFrame, from: view.window)

    if #available(iOS 11.0, *) {
        scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardFrame.height - view.safeAreaInsets.bottom, right: 0)
    } else {
        scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardFrame.height, right: 0)
    }

    scrollView.scrollIndicatorInsets = scrollView.contentInset

    let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height + keyboardFrame.height - scrollView.bounds.size.height)
    scrollView.setContentOffset(bottomOffset, animated: true)

    UIView.animate(withDuration: 0.5, animations: { () -> Void in
        self.view.layoutIfNeeded()
    })
}

===

   extension FirstViewController: UITextViewDelegate {

    func textViewDidChange(_ textView: UITextView) {

        let estimatedSize = textView.sizeThatFits(textView.frame.size)

        if estimatedSize.height > textViewMaxHeight {
            if estimatedSize.height - textViewMaxHeight < textView.font!.lineHeight && !didExpandTextView {

                didExpandTextView = true

                var contentInset:UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: self.savedKbHeight, right: 0.0)

                if let v = self.tabBarController?.tabBar {
                    contentInset.bottom -= v.frame.height
                }

                scrollView.contentInset = contentInset
                scrollView.scrollIndicatorInsets = contentInset

                if textView.isFirstResponder {
                    let fr = textView.frame
                    scrollView.scrollRectToVisible(fr, animated: false)
                }
            }

            textView.isScrollEnabled = true
            textView.showsVerticalScrollIndicator = true

        } else {
            if let lineHeight = textView.font?.lineHeight, Int(estimatedSize.height / lineHeight) != numberOfLines {
                numberOfLines = Int(estimatedSize.height / textView.font!.lineHeight)

                var contentInset:UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: self.savedKbHeight, right: 0.0)

                print("contentInset: \(contentInset)")
                scrollView.contentInset = contentInset
                scrollView.scrollIndicatorInsets = contentInset

                if textView.isFirstResponder {
                    let fr = textView.frame
                    scrollView.scrollRectToVisible(fr, animated: false)
                }

                didExpandTextView = false
            }

            textView.isScrollEnabled = false
        }
    }

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    return true
}
Raymond
  • 1,108
  • 13
  • 32
  • Quick test indicates a couple problems with this code... Even with the animation you posted, it appears the scroll content is pushed higher than necessary. Also, when tapping into either text field while the keyboard is showing pushes the text field up and out-of-frame. (I'm surprised you're not using the keyboard code I gave you for this layout.) – DonMag May 22 '20 at 14:56
  • I've already tried your code, it's still same result. With your code both issues, constraints and animation, are showing same problem as my code above. Please try inserting a textview and restricting it's height to <=120, disable scrolling in textview from IB and see the outcome. – Raymond May 22 '20 at 17:24
  • Here is link showing the outcome using your code. https://imgur.com/a/EklKnaw – Raymond May 22 '20 at 17:33
  • If you're still having trouble with this, I think the issue may be due to the "auto-vertical-centering" that you're doing. You may need to take a different approach to that. – DonMag May 28 '20 at 15:54
  • I'm still having trouble. I didn't get what you meant by auto-vertical-centering. Can you elaborate that with code? – Raymond May 28 '20 at 16:59
  • From previous questions, your using a stack view and constraints to keep the "overall content" vertically centered. Take a look at my updated GitHub repo for another approach... it may or may not be smooth enough: https://github.com/DonMag/CenteredScroll – DonMag May 28 '20 at 19:32
  • Thanks again for taking time to fix this. It seems to be working. I'll try it in my project, will update later. – Raymond May 28 '20 at 22:13
  • I've tried your solution which works fine if textview height is limited. However in my project I need to expand textview height to max 120 and after that enable scrolling in textview. I've some code to handle scrolling of textview in textViewDidChange which makes views jump up and down with expanding text view height. Would you be able to look into it? I'll update textViewDidChange code in question. – Raymond Jun 02 '20 at 16:40
  • I've updated code. – Raymond Jun 02 '20 at 16:47

1 Answers1

1

Try this approach:

    func textViewDidChange(_ textView: UITextView) {

        let estimatedSize = textView.sizeThatFits(textView.frame.size)

        textView.isScrollEnabled = estimatedSize.height > textViewMaxHeight

        if !textView.isScrollEnabled {
            let maxBottom = self.view.frame.height - self.savedKbHeight

            // Use following line if you have Extended Edges set
            // let maxBottom = self.view.frame.height - self.savedKbHeight - topLayoutGuide.length - bottomLayoutGuide.length

            var r = self.textView.frame
            r.size.height = estimatedSize.height
            let tvBottom = self.scrollView.convert(r, to: self.view).maxX
            if tvBottom > maxBottom {
                self.scrollView.scrollRectToVisible(r, animated: true)
            }
        }
    }

Edit Note: This is not intended to be Production Ready code. There are many things that affect layout / positioning, and there is no guarantee that just "dropping this in" will work in all situations.

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Thanks, much better now. The last issue is that when return key is hit, views jump and then adjusts itself. See this https://imgur.com/a/iFqlPJi How do we fix this one? – Raymond Jun 02 '20 at 18:39
  • @Raymond - take a fresh look at my GitHub repo: https://github.com/DonMag/CenteredScroll ... second tab (yellow/orange background) has `textViewDidChange()` implemented, and I'm not seeing what you're seeing. – DonMag Jun 02 '20 at 19:08
  • that's correct, it doesn't have that bug however there is another bug in this one. It hides and pushes down login button after every return. Can we keep the login button showing all the time while text view expands and pushes views up with new line? – Raymond Jun 03 '20 at 16:59
  • @Raymond - *"Can we keep the login button showing..."* -- sure. Use the button frame instead of the text view frame. – DonMag Jun 03 '20 at 17:18
  • I wish I could fix it. I tried with var r = self.loginButton.frame in textViewDidChange but it's doing some funny push up with views. Could you please do it in the code. I'm still struggling to understanding this whole scrolling stuff. – Raymond Jun 04 '20 at 14:55
  • @Raymond - I updated my GitHub repo... first tab example keeps login button showing. As a side note: I don't want this to sound bad, but Stack Overflow is not a "write my code for me" site. Ask a question... if it is answered, mark the answer Accepted and move on. I've answered several of your questions now with your response being *"oh, but now I want to do [this]"*. The more you do that, the less likely people will be willing to offer help. – DonMag Jun 04 '20 at 16:35
  • It didn't work, thanks anyways I think you have already done too much for me. I appreciate your help. – Raymond Jun 04 '20 at 21:37
  • @Raymond - curious... here's recording when I run it: https://imgur.com/a/rRHTMoB – DonMag Jun 04 '20 at 21:55
  • It pushes down login button when text view expand to show scroll. Let's leave it. I'll try to figure out myself or I'll never learn. – Raymond Jun 04 '20 at 22:03
  • @Raymond - updated my GitHub repo again... give it one more try. – DonMag Jun 05 '20 at 14:43
  • Just wanted to add that if Extended Edges are enabled then this will break scroll view when you type into text view. Wasted 2 days of time to figure out that. To fix this issue we need to use topLayoutGuide.length & bottomLayoutGuide.length in height calculations. let maxBottom = self.view.frame.height - self.savedKbHeight - topLayoutGuide.length - bottomLayoutGuide.length – Raymond Jun 06 '20 at 19:07