UITextView dynamic sizing is making the textView too tall - ios

I am using this link to resize a textView dynamically. However, I implement it into a function and it return a height of 238. Using the heirarchy viewer I can see my textView is indeed 238, but there is a ton of empty space as 238 is way taller than needed. Can anyone figure out why textView.sizeThatFits: would give me this kind of error? Here is my code.
func heightOfTextFieldForMessage(message : String) -> CGFloat {
let horizontalMargin = CGFloat(194)
let textView = UITextView()
textView.text = message
textView.font = UIFont.systemFontOfSize(14)
//This width is same as in heirachy viewer, horizontalMargin is a constant distance the textView is from edges of the screen.
let fixedWidth = self.view.frame.width - horizontalMargin
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
let height = newSize.height
print(height)
return height
}
Is it possibly because my font is smaller than the default font? I tried changing the textView's font size before resizing but it seems like like the height does change based on what I set the font to.
The Text View bottom constraint is tied to the top of the icon so as you can see the textView is way taller than needed.

Related

How to adjust the height of the textview depends on the content in correct way?

I have tried 2 more methods. But those are not giving me the correct solution.
If i tried this one
func textViewDidChange(_ textView: UITextView) {
self.comments.translatesAutoresizingMaskIntoConstraints = true
self.comments.sizeToFit()
self.comments.isScrollEnabled = false
}
i can only type a one letter per a line.
If i tried this one
func textViewDidChange(_ textView: UITextView) {
self.comments.translatesAutoresizingMaskIntoConstraints = true
self.comments.isScrollEnabled = false
let fixedWidth = passedOut.frame.size.width
let newSize = comments.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
comments.frame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
}
Its working.
But at the viewDidLoad stage, the width of textview is certain less than which i constraints to the textview. But i gave the leading, trailing , height as same as passedOut textfield to that textview.
If i start typing in textview, the width of textview will become exact size which i want.
So pls suggest me what mistake i made?
Assign a height constraint to the text view
Use the following method to change the height:
func textViewDidChange(_ textView: UITextView) {
let size = textView.bounds.size
let newSize = textView.sizeThatFits(
CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude)
)
if size.height != newSize.height {
self.userMessageTextViewHeightConstraint.constant = newSize.height
textView.layoutIfNeeded()
}
}
You can achieve this behavior by doing 2 things in textview
Disable scrolling in textview
Assign the following constraints to textview
a) leading
b) trailing
c) top
d) height >= 30 ,assuming 30 is the minimum height of your textview.
Now when your text will grow textview will grow with it.

Size UITextView to fit multiline NSAttributedString

I have a UITextView containing an NSAttributedString. I want to size the text view so that, given a fixed width, it shows the entire string without scrolling.
NSAttributedString has a method which allows to compute its bounding rect for a given size
let computedSize = attributedString.boundingRect(with: CGSize(width: 200, height: CGFloat.greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
context: nil)
But unfortunately it seems not working, since it always returns the height of a single line.
After several attempts, I figured out that the NSAttributedString I was setting had byTruncatingTail as lineBreakMode value for NSParagraphStyle (which is the default value we use in our application).
To achieve the desired behaviour I have to change it to byWordWrapping or byCharWrapping.
let paragraphStyle = NSMutableParagraphStyle()
// When setting "byTruncatingTail" it returns a single line height
// paragraphStyle.lineBreakMode = .byTruncatingTail
paragraphStyle.lineBreakMode = .byWordWrapping
let stringAttributes: [NSAttributedString.Key: Any] = [.font: UIFont(name: "Avenir-Book", size: 16.0)!,
.paragraphStyle: paragraphStyle]
let attributedString = NSAttributedString(string: string,
attributes: stringAttributes)
let computedSize = attributedString.boundingRect(with: CGSize(width: 200, height: CGFloat.greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
context: nil)
computedSize.height
Note that when setting the attributed string with byTruncatingTail value on a UILabel (where numberOfLines value is 0), the string is "automatically" sized to be multiline, which doesn't happen when computing the boundingRect.
There are other factors to keep in mind when computing NSAttributedString height for use inside a UITextView (each one of these can cause the string not to be entirely contained in the text view):
1. Recompute height when bounds change
Since height is based on bounds, it should be recomputed when bounds change. This can be achieved using KVO on bounds keypath, invalidating the layout when this change.
observe(\.bounds) { (_, _) in
invalidateIntrinsicContentSize()
layoutIfNeeded()
}
In my case I'm invalidating intrinsicContentSize of my custom UITextView since is the way I size it based on the computed string height.
2. Use NSTextContainer width
Use textContainer.width (instead of bounds.width) as the fixed width to use for boundingRect method call, since it keeps any textContainerInset value into account (although left and right default values are 0)
3. Add vertical textContainerInsets values to string height
After computing NSAttributedString height we should add textContainerInsets.top and textContainerInsets.bottom to compute the correct UITextField height (their default values is 8.0...)
override var intrinsicContentSize: CGSize {
let computedHeight = attributedText.boundingHeight(forFixedWidth: textContainer.size.width)
return CGSize(width: bounds.width,
height: computedHeight + textContainerInset.top + textContainerInset.bottom)
}
4. Remove lineFragmentPadding
Set 0 as value of lineFragmentPadding or, if you want to have it, remember to remove its value from the "fixed width" before computing NSAttributedString height
textView.textContainer.lineFragmentPadding = 0
5. Apply ceil to computed height
The height value returned by boundingRect can be fractional, if we use as it is it can potentially cause the last line not to be shown. Pass it to the ceil function to obtain the upper integer value, to avoid down rounding.
A possible way to do it, is to subclass UITextView to inform you whenever its contentSize did change (~ the size of the text).
class MyExpandableTextView: UITextView {
var onDidChangeContentSize: ((CGSize) -> Void)?
override var contentSize: CGSize {
didSet {
onDidChangeContentSize?(contentSize)
}
}
}
On the "Parent View":
#IBOulet var expandableTextView: MyExpandableTextView! //Do not forget to set the class in the Xib/Storyboard
// or
var expandableTextView = MyExpandableTextView()
And applying the effect:
expandableTextView. onDidChangeContentSize = { [weak self] newSize in
// if you have a NSLayoutConstraint on the height:
// self?.myExpandableTextViewHeightConstraint.constant = newSize.height
// else if you play with "frames"
// self?.expandableTextView.frame.height = newSize.height
}

UITextContainerView not resizing as his UITextView superview, only when rotated with an angle grater than 45°

I have a UITextView that resizes with pinch and rotation gestures simultaneously.
To resize it, I call the function textViewDidChange() in the pinch gesture handler function, while the sender state is of type .changed and after increasing the font size of the Text View.
Here is the function:
func textViewDidChange(_ textView: UITextView) {
textView.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
let newSize = textView.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
var newFrame = textView.bounds
newFrame.size = CGSize(width: newSize.width, height: newSize.height)
textView.bounds = newFrame
}
The problem is that when I rotate the Text View at an angle grater than 45 degrees, the text of the Text View is cropping. Using the Debug View Hierarchy, I noticed that the UITextContainerView of the Text View has a smaller width than its superview.
Debug View Hierarchy screen The red rectangle is the Text View and the highlighted one is its UITextContainerView.
The Text View is resizing correctly, but the UITextContainerView is not.
I don't understand why this is happening, anyone can help me?
[There was a link to a YouTube video but it has been deleted so the link was removed.]
Disable Scrolling on the textView.
This would automatically increase the height of the textView according to the contentSize.
textView.isScrollEnabled = false
In my situation, it occurs when I update height constraint of textview after an other view hide. And I use
self.viewSearchResults.isHidden = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.constraintTextViewsHeight.constant = 100
}
and problem is fixed.

UILabel's height is to big for it's width

As you can see right here, this is a UILabel with random text:
The height of that UILabel's text is too big. I just want the height to be adapted from what it is needed to be, for every different content and size of the width. Adding this extensions:
extension UILabel{
func requiredHeight() -> CGFloat{
let label:UILabel = UILabel(frame: CGRect(0, 0, self.frame.width, CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = self.font
label.text = self.text
label.sizeToFit()
return label.frame.height
}
}
extension CGRect{
init(_ x:CGFloat,_ y:CGFloat,_ width:CGFloat,_ height:CGFloat) {
self.init(x:x,y:y,width:width,height:height)
}
}
are not working aswell... Is this possible to do in Storyboard? I tried setting an aspect ratio to it, but when programmatically making the label larger this fails. Is there an easy way to set the height of a UILabel to match it's content, and maybe even in storyboard? Because of the height is not corresponding with what it is needed to be, my whole layout is screwing up. The UILabel needs to be for example 10 points from the top layout. Because the height is to big, the UILabel is setting itself far more below than needed when running it on a different device than the settee layout in storyboard.
I think you can make it setting top and bottom constraints of your UILabel in Storyboard
In this example independent of the font size of "LABEL BIG" there will always be a separation from top layout of 10 points and the "LABEL SMALL" will always be vertically separated by 8 points from "LABEL BIG"

iOS - i want to change UITextView Height from single row to some fixed hight when i enter the text in swift3

When the app loads I need to display 1 line in text view. when the user enters more words in the textview, I need to increase height automatically from 5 to 6 lines and then enable scrolling. can anyone help me?
You can always increase the frame of the textView to a fixed height on editing start (textViewDidBeginEditing), if you really wish to have it fixed.
But that would mean if user fills it up again, it would stay that way.
You could also track editing changes (editingChanged) like this:
func textViewDidChange(_ textView: UITextView) {
let width = textView.frame.size.width
let height = CGFloat.greatestFiniteMagnitude
let textViewSize = textView.sizeThatFits(CGSize(width: width, height: height)
var textViewFrame = textView.frame
textViewFrame.size = CGSize(width: width, height: textViewFrame.height)
textView.frame = newFrame
}

Resources