Self sizing uitextview till specific height - ios

I have an app which has chatting functionality where UITextview is used for entering the message. UITextview height has to be dynamic (if user enters the message, the height has to be changed according to the text length till a specific Height).
How can I achieve this?

Disable Scrolling of textView.
TO Increase Height to a specific value and then enable scrolling.
Provide a maximum height constraint then add this code to your viewController
class YourViewController: UIViewController, UITextViewDelegate
{
#IBOutlet weak var yourTextView: UITextView!
let textViewMaxHeight: CGFloat = 100
override func viewDidLoad()
{
super.viewDidLoad()
yourTextView.delegate = self
}
func textViewDidChange(textView: UITextView)
{
if textView.contentSize.height >= self.textViewMaxHeight
{
textView.scrollEnabled = true
}
else
{
textView.frame.size.height = textView.contentSize.height
textView.scrollEnabled = false
}
}
}

Add a height constraint to your textView and create an outlet so you can adjust it. Then you can use the UITexfield delegate method textViewDidChange(_ textView: UITextView) to adjust the height.
func textViewDidChange(_ textView: UITextView) {
// get the current height of your text from the content size
var height = textView.contentSize.height
// clamp your height to desired values
if height > 90 {
height = 90
} else if height < 50 {
height = 50
}
// update the constraint
textViewHeightConstraint.constant = height
self.view.layoutIfNeeded()
}
shorter version...
func textViewDidChange(_ textView: UITextView) {
let maxHeight: CGFloat = 90.0
let minHeight: CGFloat = 50.0
textViewHeightConstraint.constant = min(maxHeight, max(minHeight, textView.contentSize.height))
self.view.layoutIfNeeded()
}

In case you are subclassing UITextView this could be a solution as well:
class Message: UITextView {
var maximalSizeNotScrollable: CGFloat = 200
var minimumHeightTextView: CGFloat = 35
var textViewHeightAnchor: NSLayoutConstraint!
override var contentSize: CGSize {
didSet {
if textViewHeightAnchor != nil {
textViewHeightAnchor.isActive = false
}
textViewHeightAnchor = heightAnchor.constraint(equalToConstant: min(maximalSizeNotScrollable, max(minimumHeightTextView, contentSize.height)))
textViewHeightAnchor.priority = UILayoutPriority(rawValue: 999)
textViewHeightAnchor.isActive = true
self.layoutIfNeeded()
}
}
}
The priority needs to be smaller than 1000 otherwise you will encounter problems with _UITemporaryLayoutHeight.

I was struggling with the same problem. After reading #Anuraj's response, I dragged and dropped the height constraint of the UITextView to view controller because when textview reaches to the max height and when the scroll is enabled textview was getting smaller just to display one line, so I found the solution by giving the max height to the height constraint of textview. Textview will expand until it reaches max height, after that it will be scrollable. When you delete text, textview height will get smaller automatically.
#IBOutlet weak var txMessageHeight: NSLayoutConstraint!
let textViewMaxHeight: CGFloat = 120
func textViewDidChange(_ textView: UITextView) {
if textView.contentSize.height >= self.textViewMaxHeight{
txMessageHeight.constant = self.textViewMaxHeight
textView.isScrollEnabled = true
}else{
textView.frame.size.height = textView.contentSize.height
textView.isScrollEnabled = false
}
}

Set the Height Constraint of the TextView to <= 120(maxHeight) in StoryBoard
func textViewDidChange(_ textView: UITextView) {
if textView.contentSize.height >= maxHeight {
textView.isScrollEnabled = true
} else {
textView.isScrollEnabled = false
}
}
And if you want to reset the height of TextView when its empty, you can just write
UIView.animate(withDuration: 0.15) {
self.view.layoutIfNeeded()
}

Related

Toggle Enabling UITextView Scroll After Max Number of Lines

I have a uitextview subview in an inputContainerView as it may appear in a messaging app.
Programmatically, on load, the height anchor of the uitextview is set when the inputContainerView is initialized. This constraint works great in increasing the container height as more lines of text are typed.
My goal is to cap the height of the uitextview after reaching X number of lines where any lines typed thereafter are scrollable. And likewise, when the number of lines falls below the max, it returns to its original form - not scrollable and auto-sizing height based on content.
After multiple trials and research, I've managed to get the height to fixate once hitting the max number of lines, but I cannot seem to figure out how to return it to its original form once the number of lines have fallen below the max. It appears the uitextview height stays stuck at the height the number of lines have maxed on.
Below is relevant code:
//Container View Initialization, onLoad Frame Height is set to 60
override init(frame: CGRect) {
super.init(frame: frame)
autoresizingMask = .flexibleHeight
addSubview(chatTextView)
textView.heightAnchor.constraint(greaterThanOrEqualToConstant: frame.height - 20).isActive = true
}
//TextView Delegate
var isTextViewOverMaxHeight = false
var textViewMaxHeight: CGFloat = 0.0
func textViewDidChange(_ textView: UITextView) {
let numberOfLines = textView.contentSize.height/(textView.font?.lineHeight)!
if Int(numberOfLines) > 5 {
if !isTextViewOverMaxHeight {
containerView.textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 40).isActive = false
containerView.textView.heightAnchor.constraint(equalToConstant:
containerView.textView.frame.height).isActive = true
containerView.textView.isScrollEnabled = true
textViewMaxHeight = containerView.textView.frame.height
isTextViewOverMaxHeight = true
}
} else {
if isTextViewOverMaxHeight {
containerView.textView.heightAnchor.constraint(equalToConstant: textViewMaxHeight).isActive = false
containerView.textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 40).isActive = true
containerView.textView.isScrollEnabled = false
isTextViewOverMaxHeight = false
}
}
}
You have overcomplicated a simple problem at hand, here is how can you achieve what you want
class ViewController: UIViewController {
var heightConstraint: NSLayoutConstraint!
var heightFor5Lines: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
let textView = UITextView(frame: CGRect.zero)
textView.delegate = self
textView.backgroundColor = UIColor.red
textView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(textView)
textView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
textView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
textView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
heightConstraint = textView.heightAnchor.constraint(equalToConstant: 30.0)
heightConstraint.isActive = true
}
}
extension ViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
let numberOfLines = textView.contentSize.height/(textView.font?.lineHeight)!
if Int(numberOfLines) > 5 {
self.heightConstraint.constant = heightFor5Lines
} else {
if Int(numberOfLines) == 5 {
self.heightFor5Lines = textView.contentSize.height
}
self.heightConstraint.constant = textView.contentSize.height
}
textView.layoutIfNeeded()
}
}
How this works?:
Its simple, your textView starts off with some height (height constraint is necessary here because I haven't neither disabled its scroll nor have I provided bottom constraint, so it does not have enough data to evaluate its intrinsic height) and every time text changes you check for number of lines, as long as number of lines is less than threshold number of lines you keep increasing the height constraint of your textView so that its frame matches the contentSize (hence no scrolling) and once it hits the expected number of lines, you restrict height, so that textView frame is less than the actual content size, hence scrolls automatically
Hope this helps
It's also possible to disable initial textView's isScrollEnabled and then implement text view resizing like this:
private var heightConstraint: NSLayoutConstraint?
private let inputLinesScrollThreshold = 5
func textViewDidChange(_ textView: UITextView) {
let isConstraintActive = heightConstraint.flatMap { $0.isActive } ?? false
let lineHeight = textView.font?.lineHeight ?? 1
let linesCount = Int(textView.contentSize.height / lineHeight)
if isConstraintActive == false {
heightConstraint = textView.heightAnchor.constraint(equalToConstant: textView.frame.height)
heightConstraint?.isActive = true
textView.isScrollEnabled = true
} else {
heightConstraint?.constant = linesCount > inputLinesScrollThreshold ?
lineHeight * CGFloat(inputLinesScrollThreshold) : textView.contentSize.height
}
textView.layoutIfNeeded()
}

UITextView doesn't print text on the nextLine?

I put the UITextView inside a UIView. The UIView expands as the user types in the UITextView but the problem is that if the user types on the next line, it doesn't show the text being typed until the user types on the third line, then it shows the text printed on the second line. Same goes with the 3rd line and 4th line, etc.
How can I fix this?
func textViewDidChange(_ textView: UITextView) {
print(textView.text)
let size = CGSize(width: prayerRequest.frame.width, height: .infinity)
let estimatedSize = textView.sizeThatFits(size)
textView.constraints.forEach { (constraints) in
if constraints.firstAttribute == .height {
constraints.constant = estimatedSize.height
}
viewContainer.constraints.forEach({ (constraints) in
if constraints.firstAttribute == .height {
constraints.constant = estimatedSize.height
viewContainer.layoutIfNeeded()
}
})
}
}
If you're using interface builder try setting the number of lines to 0.
Or from code textView.textContainer.maximumNumberOfLines = 10
First of all, you don't need to set height for textView's Parent. Even if you do set the low priority for textView's parent.
See the image, Purple being parent and Yellow is textView.
In viewDidLoad, add the following code:
textView.textContainerInset = UIEdgeInsets.zero
textView.textContainer.lineFragmentPadding = 0
Then, implement textView Delegate method:
extension ViewController : UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
let textHeight = textView.attributedText.boundingRect(with:
CGSize.init(width: textView.frame.width, height: .infinity),
options:[.usesLineFragmentOrigin, .usesFontLeading],
context: nil).height
if previousHeight != textHeight {
previousHeight = textHeight
print("Height text: \(textHeight)")
textViewHeight.constant = textHeight
self.view.layoutIfNeeded()
textView.setContentOffset(.zero, animated: false)
}
}
}
textViewHeight is height constraint of textView.
Initialize var previousHeight = CGFloat(0) as instance variable. It will help limit calls to layoutIfNeeded to only when the height is changed.
Bottom constraint of the textView will expand parent view. So you do not need to set Parent's height as well.

Resize TextView Heigh AND Width according to text - Swift

I'm trying to auto resize a UITextView in a TableView Custom Cell using the below function in my TableViewController. It works perfectly: it resize my TextView heigh according to the text length BUT I cannot find any help on how to adjust the TextView width! I already set the constraints to bounds it on the width of the cell but is not working. there is any function that can help me resize the textView width as the heigh?
Thanks.
this is my output now
func adjustUITextViewHeight(arg : UITextView)
{
arg.translatesAutoresizingMaskIntoConstraints = true
arg.sizeToFit()
arg.isScrollEnabled = false
}
I solved using this function:
func adjustUITextViewHeight(arg : UITextView, viewBox : UIView)
{
arg.isScrollEnabled = false
let size = CGSize(width: viewBox.frame.width, height: .infinity)
let estimatedSize = arg.sizeThatFits(size)
arg.constraints.forEach { (constraint) in
if constraint.firstAttribute == .height {
constraint.constant = estimatedSize.height
}
}
}
Here is the link to the video-tutorial: https://www.youtube.com/watch?v=0Jb29c22xu8

UITextView content size not increasing in iOS 8

I created a UITextView and set the isScrollEnabled to true, after I paste two lines text in the textView, then I input the text by keyboard, but the textView's contentSize did not increase, it only happened in iOS 8 and only after I paste two lines text.
func textViewDidChange(_ textView: UITextView) {
if textView.text.isEmpty {
updateCommentViewHeight(45.0)
} else {
let height = calculateTextSize(with: textView).height //calulate textview height by content text
updateCommentViewHeight(height + 30.0)
if commentInputView.bounds.height > height + 29.0 {
textView.isScrollEnabled = true
} else {
textView.isScrollEnabled = false
}
}
}
private func updateCommentViewHeight(_ height: CGFloat) {
constrain(commentInputView, replace: group, block: { (commentInputView) in
commentInputView.height == height
})
view.layoutIfNeeded()
}

Adjusting height of UITextView to its text does not work properly

I wrote following code to fit UITextView's height to its text.
The size changes but top margin relative to first line of text differ every other time when I tap enter key on keyboard to add new line.
Setting
xCode 7.3
Deployment target: iOS 9
import UIKit
class ViewController: UIViewController, UITextViewDelegate {
lazy var textView: UITextView = {
let tv = UITextView(frame: CGRectMake(20, 200, (self.view.frame.width - 40), 0) )
tv.backgroundColor = UIColor.lightGrayColor()
return tv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview( textView )
textView.delegate = self
let height = self.height(textView)
let frame = CGRectMake(textView.frame.origin.x, textView.frame.origin.y, textView.frame.width, height)
textView.frame = frame
}
func textViewDidChange(textView: UITextView) {
let frame = CGRect(x: textView.frame.origin.x, y: textView.frame.origin.y, width: textView.frame.width, height: height(textView) )
textView.frame = frame
}
func height(textView: UITextView) -> CGFloat {
let size = CGSizeMake(textView.frame.size.width, CGFloat.max)
let height = textView.sizeThatFits(size).height
return height
}
}
I tried few other ways to fit UITextView height but they just acted the same say.
To fix this, subclass UITextView and override setContentOffset to allow scrolling only if the content height is larger than the intrinsic content height. Something like:
override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
let allowScrolling = (contentSize.height > intrinsicContentSize.height)
if allowScrolling {
super.setContentOffset(contentOffset, animated: animated)
}
}
For auto-growing dynamic height text view, the caret moves on starting a new line. At this moment, the size of the text view hasn't grown to the new size yet. The text view tries to make the caret visible by scrolling the text content which is unnecessary.
You may also need to override intrinsicContentSize too.

Resources