Understanding UIScrollView contentSize.height - ios

I'm kind of confused by this. I have a very simple setup. I have a UIScrollView that is constrained to the superview of a ViewController with 0,0,0,0. And then a UILabel that is constrained to the UIScrollView with 8,8,8,8.
I have a String extension to get the height of the label with a specific font.
extension String {
func heightWithConstrainedWidth(width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedString.Key.font: font], context: nil)
return boundingBox.height
}
}
In viewDidLoad, I set up my label. (UILabel is set for number of lines to 0 and word wrap).
let text = "some really long text" // actually really long text in my sample
let width = view.frame.width
let font: UIFont! = UIFont(name: "HelveticaNeue-UltraLight", size: 42)
let height = text.heightWithConstrainedWidth(width: width, font: font)
print("height first: \(height)") // 1080
Then I set my scrollView.contentSize to that size
scrollView.contentSize = CGSize(width: view.frame.width, height)
print("new height: \(scrollView.contentSize.height)") // 1080
But in scrollViewDidScroll, if I print out the height there
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("scrollView.contentSize.height: \(scrollView.contentSize.height)") // 3440
}
I was wondering why in scrollViewDidScroll the height of the scrollView's contentSize is 3440 and not 1080. It's 1080 after I put in the text for the label and set up the content size.
I would think if the size is 3440, I would see that after I set the scrollView's contentSize and it wouldn't change in the scrollViewDidScroll delegate method. Is this because I'm using Auto Layout and Auto Layout doesn't calculate the size of the contentSize until you start scrolling or something?

Related

Increasing height of button as text size increases in Swift

I am very new to autolayout and I have been struggling with one thing.
The Roll btn in the BottomView - I am trying to increase it's height according to the text size. As you can see, the height of the textlabel of btn in increasing, but the height of btn is not.
The constraints that I have applied:
1)Horizontallly and verticallly centered in superview
2)Leading and trailing from superview
3) I tried setting height constraint to greaterThanEqualTo a constant also. But that didnt work
4)I tried SizeToFit also. Didn't work
5)I have set link lineBreak as WordWrap and number of lines in titleLabel as 0. Still its not working
i used this solution to change height of uilabal dynamically
you can get this extension to get height and width of string
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return ceil(boundingBox.height)
}
func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return ceil(boundingBox.width)
}
}
since you are using storyboard set height and width constant then link them to viewcontroller script
YourContrainsHeight.constant = SomeString.height
YourContrainsWidth.constant = SomeString.width
When you modify the .titleLabel of a UIButton, auto-layout doesn't take your changes into account.
To get a properly auto-sizing multi-line button, you need to subclass it and return a valid .intrinsicContentSize.
Here is a quick example:
#IBDesignable
class MultilineButton: UIButton {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
setNeedsLayout()
}
func commonInit() -> Void {
self.titleLabel?.numberOfLines = 0
self.titleLabel?.textAlignment = .center
self.contentEdgeInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0)
}
override var intrinsicContentSize: CGSize {
let size = self.titleLabel!.intrinsicContentSize
return CGSize(width: size.width + contentEdgeInsets.left + contentEdgeInsets.right, height: size.height + contentEdgeInsets.top + contentEdgeInsets.bottom)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
}
}
The class is marked #IBDesignable so you can use it in Storyboard / Interface Builder get accurate results.
Here's a look (in Storyboard) of 3 instances of this button. First with a short title, so it doesn't need to wrap; next with a title that does wrap; and third with a title that has embedded newlines:
Note that this example explicitly sets the Content Edge Insets to 8-pts on all 4 sides. If you want to be able to change that for individual buttons, it will need a little editing.

How to get the content Height of dynamically changing Text in textview?

I have a dynamically changing text in TextView.I could not be able to get the content Height of TextView.
Here is what i tried.
let height = self.tvComment.contentSize.height
print("height",height)
let contentSizeComment = self.tvComment.sizeThatFits(self.tvComment.bounds.size)
print("height",contentSizeComment)
Why it's not getting the content height of TextView?
Hope you understand my problem.
Thanks in Advance
Usethis method to get the height -
func heightForString(text:String, font:UIFont, width:CGFloat) -> CGFloat{
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.size.height
}
in textdidchange you can use this code,in order to resize when textchange
//approxi should be the width of your textview
let approxi = view.frame.width - 90
//size is the max width and height of textview,1000 can be what ever you want
let size = CGSize(width: approxi, height: 1000)
//dont forget to put your font and size
//chey is the text of thetext view
let attributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 15)]
let estim = NSString(string: chey).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil)
//estim is height
above it the first method,second method will come in edit
second method is
func pva() {
//what was the name of my textfield
let fixedwidth = what.frame.size.width - 40
let newsize = what.contentSize.height
self.hrightext.constant = newsize
self.view.layoutIfNeeded()
}
note: both are tested and works in swift4
Your just write code in below delegate of UITextView:-
func textViewDidChange(_ textView: UITextView!) {
let height = self.tvComment.contentSize.height
print("height",height)
let contentSizeComment = self.tvComment.sizeThatFits(self.tvComment.bounds.size)
print("height",contentSizeComment)
}
I hope it help you,
Thank you.

How can I make a TextField width adapt to his content?

Is there any option that make a TextField adapt to his content?
There is a way to do it manually by adjusting the frame of the textview when a change in it occurs.
var text: String = "Some text is just here..."
let size: CGSize = text.size(attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 14.0)])
^The code will give you the size needed for the text to be fully displayed and you can resize you textView according to it. //be aware there might be some paddings inside the textview
extension String {
func heightWithConstrainedWidth(width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return boundingBox.height
}
}
^If you need dynamic height (multi-lines) constraining the width will help you with the task.
I'm not completely sure what you are trying to accomplish, but I hope this helps a little.
Create a oultet for height constraint of textview. After:
textView.delegate = self
textView.isScrollEnabled = false
func textViewDidChange(_ textView: UITextView) {
let fixedWidth = textView.frame.size.width
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
var newFrame = textView.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
heightTextview.constant = newFrame.size.height //update constraint constant with new height
}
textView will change height for fit multiple line content. It works well

Get height of UIView based on specific width before showing view

I have a UIView which I have designed in Interface Builder. It basically consists out of a header image and some text (UILabel) below. The view is being shown modally with a custom Transition and doesn't fill the whole screen.
There is like a 20 pixels margin on the left and right and 40 px on the top. The UILabel gets filled with some text that's coming from the web. What I want do do, is to find (or should I say predict) the height of the whole view for a specific width. How can I do that?
I had similar problem, but instead of calculating font size and image height you can use this UIView extension that will autosize your view with max width:
extension UIView {
func autosize(maxWidth: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
let dummyContainerView = UIView(frame: CGRect(x: 0, y: 0, width: maxWidth, height: 10000000))
dummyContainerView.addSubview(self)
dummyContainerView.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
dummyContainerView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0).isActive = true
dummyContainerView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0).isActive = true
setNeedsLayout()
layoutIfNeeded()
removeFromSuperview()
frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
translatesAutoresizingMaskIntoConstraints = true
}
}
Using this approuch you don't have to worry about your content inside the view.
To use it:
let customView: CustomView = ... //create your view
... // configure all data on your view, e.g. labels with correct text
customView.autosize(maxWidth: 150) // resize your view
view.addSubview(customView) // add your view to any view
You need to have both the picture and the label before calculating the aspected size.
I guess you should use something like this (maybe adding the vertical inter-distance between the imageView and the Label to the sum, and maybe removing the lateral margins from the width):
objective C :
- (CGFloat)preferredHeightFromWidth:(CGFloat)width text:(NSString *)text font:(UIFont *)font image:(UIImage *)image
{
// Calculate label height
CGFloat labelHeight = [text
boundingRectWithSize:CGSizeMake(width, 10000)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:[[NSStringDrawingContext alloc] init]
].size.height;
// Calculate image height
CGFloat ratio = image.size.height/ image.size.width;
CGFloat imageHeight = (ratio * width);
// Do the sum
return labelHeight + imageHeight;
}
Swift:
func preferredHeight(width: CGFloat, text: NSString, font: UIFont, image: UIImage) -> CGFloat {
// Calculate Label Height
let labelRect = text.boundingRect(
with: CGSize.init(width: width, height: 10000),
options: .usesLineFragmentOrigin,
attributes: [NSFontAttributeName : font],
context: NSStringDrawingContext())
let labelHeight = labelRect.height
// Calculate Image Height
let ratio = image.size.height / image.size.width
let imageHeight = ratio / width
// Calculate Total Height
let height = labelHeight + imageHeight
// Return Height Value
return height
}
(Thanks to Christopher Hannah for swift version)
Here is the same answer as Alberto, but I have changed it into Swift 3.
func preferredHeight(width: CGFloat, text: NSString, font: UIFont, image: UIImage) -> CGFloat {
// Calculate Label Height
let labelRect = text.boundingRect(
with: CGSize.init(width: width, height: 10000),
options: .usesLineFragmentOrigin,
attributes: [NSFontAttributeName : font],
context: NSStringDrawingContext())
let labelHeight = labelRect.height
// Calculate Image Height
let ratio = image.size.height / image.size.width
let imageHeight = ratio / width
// Calculate Total Height
let height = labelHeight + imageHeight
// Return Height Value
return height
}

Swift How to calculate one line text height from its font

I ran into an issue where I needed to animate translating a label vertically the same distance of a textField's text height. In most cases just textField.bounds.heigt but if the textField's height is bigger than the text height it will not be any good for me. So I need to know:
How to calculate the line height of the string text from its UIFont?
Regarding the duplicate:
There's a little bit different of what I need. that answer(which I've referenced in my answer) get the total height depending on 1) the string 2) the width 3) the font. What I needed is one line height dpending only on the font.
UIFont has a property lineHeight:
if let font = _textView.font {
let height = font.lineHeight
}
where font is your font
I have been searching for a way to do that and find this answer where it has a String extension to calculate the size for the string and a given font. I have modified it to do what I want (get the line height of text written using a font.):
extension UIFont {
func calculateHeight(text: String, width: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
let boundingBox = text.boundingRect(with: constraintRect,
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [NSAttributedStringKey.font: self],
context: nil)
return boundingBox.height
}
}
I hope this helps someone looking for it. (may be myself in the future).
I've used this String extension in the past to draw some text as opposed to creating a UILabel somewhere. I don't like the fact that I can't seem to get the real height of the specific text I want to draw (not every string contains capital letters or characters with descenders, etc.) I've used a couple of enums for horizontal and vertical alignment around the given point. Open to ideas on the vertical height.
public func draw(at pt: CGPoint,
font: UIFont? = UIFont.systemFont(ofSize: 12),
color: UIColor? = .black,
align: HorizontalAlignment? = .Center,
vAlign: VerticalAlignment? = .Middle)
{
let attributes: [NSAttributedString.Key : Any] = [.font: font!,
.foregroundColor: color!]
let size = self.boundingRect(with: CGSize(width: 0, height: 0),
options: [ .usesFontLeading ],
attributes: [ .font: font! ],
context: nil).size
var x = pt.x
var y = pt.y
if align == .Center {
x -= (size.width / 2)
} else if align == .Right {
x -= size.width
}
if vAlign == .Middle {
y -= (size.height / 2)
} else if vAlign == .Bottom {
y -= size.height
}
let rect = CGRect(x: x, y: y, width: size.width, height: size.height)
draw(in: rect, withAttributes: attributes)
}

Resources