Search code examples
swiftxcodeautolayoutuitextviewuistackview

Expanding UITextView inside a Stack View with scrolling enabled


I am trying to achieve something with Auto Layout and Stackview. I have a Vertical Stackview with a UIView, UITextView, and UIView in it as shown below.

enter image description here

I have checked out the previous answers here but couldn't find a clean solution to achieve this.

The UITextView is editable and must expand as the user types in it - for this I have disabled the scroll for the UITextView in the IB. I also have a height constraint set on the UITextView set to "Greater than or equal to" 10 and number of lines set to 0, so that the UITextView takes the intrinsic height at run time while the user types. I also want the expansion of the UITextView to continue until the UIStackView bottom edge reaches the Keypad accessory's top edge. I have been somewhat able to achieve the expanding UITextView part with my stack view pinned to the top, trailing, and leading edges of the Superview but I keep getting an error that the stackview needs Y position or height which is understandable but if I give it a fixed height or bottom constraint then the UITextView simply won't expand.

I also am not sure how to stop the expansion of the UITextView when it reaches the top edge of the Keyboard accessory view.

The code that I have so far is

let allowedHeight = self.view.frame.height - (self.keyboardHeight! + self.accessory.frame.height)

Basically find the allowed height in the view frame by subtracting the keyboardheight+Accessory view height. This calculation happens correctly. Next I do this (inside textViewDidChange) in hopes that just enabling/disabling the scroll on the UITextView would work but it clearly doens't since the whole text view then weirdly jumps up and down with every key stroke.

func textViewDidChange(_ textView: UITextView) {
   if stackView.frame.height >= allowedHeight{
            textView.isScrollEnabled = true
        }

   if stackView.frame.height < allowedHeight{
            textView.isScrollEnabled = false
        }
}

What is the best way to achieve what I am looking to do?


Solution

  • Couple notes...

    I think you'll be better off setting the Max Height of your UITextView rather than of your UIStackView. The Text View will expand / contract based on its content, so you can set a <= height constraint.

    Calculate the "elementsHeight" - the vertical size used by your top and bottom views, plus any spacing - and set the "max text view height" to the view height minus elementsHeight ... and subtract keyboard height when visible.

    Update the text view's height constraint constant when "max text view height" changes.

    Then set up an Observer for the text view's .contentSize ... and enable / disable scrolling based on .contentSize.height compared to "max text view height".

    Needs a bit of hoop-jumping, to make sure you update sizes when subviews are laid-out, and to make sure you don't get in a recursion loop.

    This is how I set it up:

    enter image description here

    The initial height constraint on the text view is <= 40 - but that doesn't really matter, as it will be changed via code every time the views layout differently.

    I put up an example on GitHub -- it works, but is really just a starting-point. Take a look if you're interested, and pick it apart. https://github.com/DonMag/ExpandingTextView