adjustsFontSizeToFitWidth making text too small/ trying to check font size - ios

I am creating multiple buttons in my code, each one grabbing its text from separate arrays. I AM using the adjustsFontSizeToFitWidth = true (That was the only thing I could find that would automatically resize the text, so it fits within the frame of the button). However, when some the text adjusts, it can get ridiculously small; it's definitely smaller than it needs to be. Is there any way you can make sure the text only sizes as much as it needs to?
override func viewDidLoad() {
super.viewDidLoad()
correctWord.text = correctAnswer[questionNumber]
correctWord.frame = CGRectMake(0, 0, 1 * self.view.frame.width / 4, 1.5 * self.view.frame.height / 22)
correctWord.center = CGPointMake(self.view.bounds.width / 2, 14.25 * self.view.bounds.height / 22)
correctWord.textAlignment = NSTextAlignment.Center
correctWord.numberOfLines = 1
correctWord.font = UIFont(name: "Chalkboard SE", size: 60)
correctWord.textColor = UIColor.greenColor()
correctWord.adjustsFontSizeToFitWidth = true
correctWord.baselineAdjustment = .AlignCenters
correctWord.minimumScaleFactor = 0.1
self.view.addSubview(correctWord)
I DID try a code that I saw while searching (found below). I created a function for the code to update the text size, and I called it before self.view.addSubview(correctWord). But, the button never appears. And, is there a way you can check to see what the font size actually is? When I print currentSize to the logs to determine the font size, it always says "0". I am trying to determine which text's size is the smallest, so all of the buttons can be set to the same font size.
func adjustFontSizeToFitRect(rect : CGRect){
if correctWord.text == nil{
return
}
correctWord.frame = rect
let maxFontSize: CGFloat = 100.0
let minFontSize: CGFloat = 5.0
var q = Int(maxFontSize)
var p = Int(minFontSize)
let constraintSize = CGSize(width: rect.width, height: CGFloat.max)
while(p <= q){
currentSize = (p + q) / 2
correctWord.font = UIFont(name: "Chalkboard SE", size: CGFloat(currentSize))
let textRect = correctWord.text!.boundingRectWithSize(constraintSize, options: .UsesLineFragmentOrigin, attributes: nil, context: nil)
let labelSize = textRect.size
if labelSize.height < correctWord.frame.height && labelSize.height >= correctWord.frame.height-10 && labelSize.width < correctWord.frame.width && labelSize.width >= correctWord.frame.width-10 {
break
}else if labelSize.height > correctWord.frame.height || labelSize.width > correctWord.frame.width{
q = currentSize - 1
}else{
p = currentSize + 1
}
}
}

Related

How to change view size according to its content in iOS swift

I am working on photo editing app. I want to put some label and images in a view which is named as 'stickerView'. All I want when content size in that view change the stickerview also change its height and width accordingly.
For sticker view I am using https://github.com/injap2017/StickerView this library.
its working fine with images but with label its not adjust label font according to view.
I use this class to set the font according to the view
import UIKit
class FlexiFontLabel: UILabel {
// Boundary of minimum and maximum
private let maxFontSize = CGFloat(100)
private let minFontSize = CGFloat(15)
// Margin of error is needed in binary search
// so we can return when reach close enough
private let marginOFError = CGFloat(0.5)
// layoutSubviews() will get called while updating
// font size so we want to lock adjustments if
// processing is already in progress
private var isUpdatingFontSize = false
// Once this is set to true, the label should only
// only support multiple lines rather than one
var doesAdjustFontSizeToFitFrame = false
{
didSet
{
if doesAdjustFontSizeToFitFrame
{
numberOfLines = 0
}
}
}
// Adjusting the frame of the label automatically calls this
override func layoutSubviews()
{
super.layoutSubviews()
// Make sure the label is set to auto adjust the font
// and it is not currently processing the font size
if doesAdjustFontSizeToFitFrame
&& !isUpdatingFontSize
{
adjustFontSizeIfRequired()
}
}
/// Adjusts the font size to fit the label's frame using binary search
private func adjustFontSizeIfRequired()
{
guard let currentText = text,
var currentFont = font else
{
print("failed")
return
}
// Lock function from being called from layout subviews
isUpdatingFontSize = true
// Set max and min font sizes
var currentMaxFontSize = maxFontSize
var currentMinFontSize = minFontSize
while true
{
// Binary search between min and max
let midFontSize = (currentMaxFontSize + currentMinFontSize) / 2;
// Exit if approached minFontSize enough
if (midFontSize - currentMinFontSize <= marginOFError)
{
// Set min font size and exit because we reached
// the biggest font size that fits
currentFont = UIFont(name: currentFont.fontName,
size: currentMinFontSize)!
break;
}
else
{
// Set the current font size to the midpoint
currentFont = UIFont(name: currentFont.fontName,
size: midFontSize)!
}
// Configure an attributed string which can be used to find an
// appropriate rectangle for a font size using its boundingRect
// function
let attribute = [NSAttributedString.Key.font: currentFont]
let attributedString = NSAttributedString(string: currentText,
attributes: attribute)
let options: NSStringDrawingOptions = [.usesLineFragmentOrigin,
.usesFontLeading]
// Get a bounding box with the width of the current label and
// an unlimited height
let constrainedSize = CGSize(width: frame.width,
height: CGFloat.greatestFiniteMagnitude)
// Get the appropriate rectangle for the text using the current
// midpoint font
let newRect = attributedString.boundingRect(with: constrainedSize,
options: options,
context: nil)
// Get the current area of the new rect and the current
// label's bounds
let newArea = newRect.width * newRect.height
let currentArea = bounds.width * bounds.height
// See if the new frame is lesser than the current label's area
if newArea < currentArea
{
// The best font size is in the bigger half
currentMinFontSize = midFontSize + 1
}
else
{
// The best font size is in the smaller half
currentMaxFontSize = midFontSize - 1
}
}
// set the font of the current label
font = currentFont
// Open label to be adjusted again
isUpdatingFontSize = false
}
}
here I set sticker view and label:
var testLabel = FlexiFontLabel(frame: CGRect.init(x: 0, y: 0, width: 300, height: 50))
testLabel.text = "Test Label"
testLabel.textAlignment = .left
testLabel.backgroundColor = .blue
testLabel.adjustsFontForContentSizeCategory = true
testLabel.numberOfLines = 1
testLabel.adjustsFontSizeToFitWidth = true
testLabel.minimumScaleFactor = 0.2
testLabel.font = testLabel.font.withSize(testLabel.frame.height * 2/3)
testLabel.doesAdjustFontSizeToFitFrame = true
let stickerView2 = StickerView.init(contentView: testLabel)
stickerView2.center = CGPoint.init(x: 100, y: 100)
stickerView2.delegate = self
stickerView2.setImage(UIImage.init(named: "Close")!, forHandler: StickerViewHandler.close)
stickerView2.setImage(UIImage.init(named: "Rotate")!, forHandler: StickerViewHandler.rotate)
stickerView2.showEditingHandlers = false
self.view.addSubview(stickerView2)
its some how working fine for me but when I change font size of label using slider its size does not change.
So my question is how to change font size and stickerview size accordingly.
if I change font size using slider label size and stickerview size change accordingly and if I change view width and height its change label font size accordingly.
help will be appreciated thanks.

How to set width of UIImageView based on UILabel row width

I have recently begun work on an app using Swift in Xcode and am trying to create a text bubble. To do this, I need to get the width of the longest row of text in a multi-row UILabel. For example, if I have this text (I automatically set line breaks after a certain length):
Hello there, this is
an example piece of text
I would like to return the width of the text in the second row. I have already tried using sizeToFit() which would drastically simplify my work, but because of my other code, this is not an option as it causes other problems (my code is below). Is there a purely programmatic way to get this value without using sizeToFit()? Any help would be much appreciated. My code:
bubbleContents.text = textMessage
bubbleContents.numberOfLines = 0
bubbleContents.lineBreakMode = .byWordWrapping
bubbleContents.bounds.size.width = 2000
var widthText = bubbleContents.intrinsicContentSize.width
bubbleContents.bounds.size.width = 266
print(textMessage)
print(widthText)
if widthText > 266 {
let numRows = Int(widthText/266)
print(numRows)
//bubbleContents.frame.origin.y += CGFloat((Double(numRows)*10.25))
var currentHeight = 44.0
currentHeight += Double((Double(numRows)*20.5))
bubbleContents.bounds.size.height = CGFloat(currentHeight)
heightOfCell = Double(currentHeight)
let originalTransform = self.bubbleContents.transform
let scaledTransform = originalTransform
let scaledAndTranslatedTransform = scaledTransform.translatedBy(x: 0, y: CGFloat(Double(numRows)*20.5))
//self.bubbleContents.transform = scaledAndTranslatedTransform
}
else {
heightOfCell = 44.0
}
bubble.frame = CGRect(x: 0, y: 0, width: Double(widthText + 30), height: heightOfCell - 4)
bubbleContents.center.y = bubble.center.y
Here is an image of what my current text bubbles look like:
You can use NSAttributedString,boundingRect(with:options:context:) method, begin by creating NSAttributedString with attributes such as font of your UILabel
let attributes: [NSAttributedString.Key : Any] = [.font: bubbleContents.font]
let atStr = NSAttributedString(string: textMessage, attributes: attributes)
Now use atStr.boundingRect(with:options:context:) method, like so:
let bounds = CGSize(width: 266.0, height: .greatestFiniteMagnitude)
let bubbleSize = atStr.boundingRect(with: bounds, options: [.usesLineFragmentOrigin, .usesFontLeading, .usesDeviceMetrics], context: nil).size
Usage:
bubble.frame.size.width = bubbleSize.width
bubble.frame.size.height = max(bubbleSize.height, 44.0)

Swift 3 - Adjust Font Size to Fit Width, Multiple Lines

I have a UILabel and it is set to 42.0 pt font, and the width of the label is set using autoconstraints based on factors other than the label itself (aka the things to the right and left of the label determine the label's width).
I would like to auto-adjust the font size to fit the width of the label, however also break to two lines when it can. Similar to this:
I know you can adjust the font size to fit the width of the label, but only when the number of lines is set to 1.
How would I accomplish this?
This will work..
Set minimum scale factor for your label. as shown in this image.
Set adjustsFontSizeToFitWidth to true
Set number of lines = 2 // or zero (0) if you want more number of lines
Set line breaking mode to '.byTruncatingTail' for 2 lines
Swift 5
Set number of lines zero for dynamic text information, it will be useful for varying text.
var label = UILabel()
let stringValue = "A label\nwith\nmultiline text."
label.text = stringValue
label.numberOfLines = 2 // 0
label.lineBreakMode = .byTruncatingTail // or .byWrappingWord
label.minimumScaleFactor = 0.5 // It is not required but nice to have a minimum scale factor to fit text into label frame
label.adjustsFontSizeToFitWidth = true //needed in Swift 5
Also, don't set height constraint for your label more than 2 lines.
Interesting question. Here's my solution:
let labelText = self.mylabel.text //where mylabel is the label
let labelSeperated = self.labelText.components(seperatedBy: " ")
if labelSeperated.count > 1 {
myLabel.lineBreakMode = .byWordWrapping
myLabel.numberOfLines = 0
} else {
myLabel.numberOfLines = 1
myLabel.adjustsFontSizeToFitWidth = true
}
Put this code where the label will be changed. It sets the line number to 0 if there are two or more numbers, otherwise set to 1 line only.
If you want to resize multi-line labels, check out this blog post.
Swift 5
func setFontForLabel(label:UILabel, maxFontSize:CGFloat, minFontSize:CGFloat, maxLines:Int) {
var numLines: Int = 1
var textSize: CGSize = CGSize.zero
var frameSize: CGSize = CGSize.zero
let font: UIFont = label.font.withSize(maxFontSize)
frameSize = label.frame.size
textSize = (label.text! as NSString).size(withAttributes: [NSAttributedString.Key.font: font])
// Determine number of lines
while ((textSize.width/CGFloat(numLines)) / (textSize.height * CGFloat(numLines)) > frameSize.width / frameSize.height) && numLines < maxLines {
numLines += 1
}
label.font = font
label.adjustsFontSizeToFitWidth = true
label.numberOfLines = numLines
label.minimumScaleFactor = minFontSize/maxFontSize
}
Swift 3
I looked at the post that paper111 posted. Unfortunately it's in Obj-C and the sizeWithFont: ,constrainedToSize: , lineBreakMode: method has been deprecated. (- - );
His answer was good, but still didn't provide a fixed size. What I did was to start with a UILabel that had everything but the height (this is probably the same for most people).
let myFrame = CGRect(x: 0, y:0, width: 200, height: self.view.height)
let myLbl = UILabel(frame: myFrame)
let finalHeight:CGFloat = 300
myLbl.font = UIFont(name: "Chalkduster", size: 16.0)
myLbl.lineBreakMode = .byWordWrapping
myLbl.numberOfLines = 0
myLbl.text = "Imagine your long line of text here"
addSubview(myLbl)
myLbl.sizeToFit()
guard myLbl.frame.height > finalHeight else { return }
var fSize:CGFloat = 16 //start with the default font size
repeat {
fSize -= 2
myLbl.font = UIFont(name: "Chalkduster", size: fSize)
myLbl.sizeToFit()
} while myLbl.frame.height > finalHeight
You can see that there's a guard blocking the resize if it's not needed. Also, calling sizeToFit() many times isn't ideal, but I can't think of another way around it. I tried to use myLbl.font.withSize(fSize) in the loop but it wouldn't work, so I used the full method instead.
Hope it works for you!
Swift 5
extension UILabel{
func adjustsFontSizeToFit(maxFontSize:CGFloat,width:CGFloat,height:CGFloat) {
self.numberOfLines = 0
var fontSize:CGFloat = maxFontSize
if self.sizeThatFits(CGSize(width: width, height: .infinity)).height > height{
while self.sizeThatFits(CGSize(width: width, height: .infinity)).height > height{
fontSize -= 1
self.font = self.font.withSize(fontSize)
}
}
}
}
#Krunal's answer helped me but it doesn't work when you have unknown number of lines so here's the solution I came up with. You can also set the maximum and minimum font size. Hope this helps someone!
Swift 2.2 - Sorry, haven't migrated to Swift 3 yet.
func setFontForLabel(label:UILabel, maxFontSize:CGFloat, minFontSize:CGFloat, maxLines:Int) {
var numLines: Int = 1
var textSize: CGSize = CGSizeZero
var frameSize: CGSize = CGSizeZero
var font: UIFont = UIFont.systemFontOfSize(maxFontSize)
frameSize = label.frame.size
textSize = (label.text! as NSString).sizeWithAttributes([NSFontAttributeName: font])
// Determine number of lines
while ((textSize.width/CGFloat(numLines)) / (textSize.height * CGFloat(numLines)) > frameSize.width / frameSize.height) && numLines < maxLines {
numLines += 1
}
label.font = font
label.adjustsFontSizeToFitWidth = true
label.numberOfLines = numLines
label.minimumScaleFactor = minFontSize/maxFontSize
}

How to find actual number of lines of UILabel?

How can I find the actual number of lines of a UILabel after I have initialized it with a text and a font? I have set its numberOfLines property to 0, so it will expand to however many lines are necessary. But then, how can I find out how many lines it finally got after I set its text?
I found similar questions, but none seems to provide a concise answer and it seems to me that it must be really easy to get it without any overhead on juggling around with the boundingRectWithSize or sizeWithFont,...
None of these worked for me. Below one did,
Swift 4.2:
extension UILabel {
func calculateMaxLines() -> Int {
let maxSize = CGSize(width: frame.size.width, height: CGFloat(Float.infinity))
let charSize = font.lineHeight
let text = (self.text ?? "") as NSString
let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
let linesRoundedUp = Int(ceil(textSize.height/charSize))
return linesRoundedUp
}
}
Swift 4/4.1:
extension UILabel {
func calculateMaxLines() -> Int {
let maxSize = CGSize(width: frame.size.width, height: CGFloat(Float.infinity))
let charSize = font.lineHeight
let text = (self.text ?? "") as NSString
let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
let linesRoundedUp = Int(ceil(textSize.height/charSize))
return linesRoundedUp
}
}
Swift 3:
extension UILabel {
func calculateMaxLines() -> Int {
let maxSize = CGSize(width: frame.size.width, height: CGFloat(Float.infinity))
let charSize = font.lineHeight
let text = (self.text ?? "") as NSString
let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
let linesRoundedUp = Int(ceil(textSize.height/charSize))
return linesRoundedUp
}
}
Swift 5 (IOS 12.2)
Get max number of lines required for a label to render the text without truncation.
extension UILabel {
var maxNumberOfLines: Int {
let maxSize = CGSize(width: frame.size.width, height: CGFloat(MAXFLOAT))
let text = (self.text ?? "") as NSString
let textHeight = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil).height
let lineHeight = font.lineHeight
return Int(ceil(textHeight / lineHeight))
}
}
Get max number of lines can be displayed in a label with constrained bounds. Use this property after assigning text to label.
extension UILabel {
var numberOfVisibleLines: Int {
let maxSize = CGSize(width: frame.size.width, height: CGFloat(MAXFLOAT))
let textHeight = sizeThatFits(maxSize).height
let lineHeight = font.lineHeight
return Int(ceil(textHeight / lineHeight))
}
}
Usage
print(yourLabel.maxNumberOfLines)
print(yourLabel.numberOfVisibleLines)
Firstly set text in UILabel
First Option :
Firstly calculate height for text according to font :
NSInteger lineCount = 0;
CGSize labelSize = (CGSize){yourLabel.frame.size.width, MAXFLOAT};
CGRect requiredSize = [self boundingRectWithSize:labelSize options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName: yourLabel.font} context:nil];
Now calculate number of lines :
int charSize = lroundf(yourLabel.font.lineHeight);
int rHeight = lroundf(requiredSize.height);
lineCount = rHeight/charSize;
NSLog(#"No of lines: %i",lineCount);
Second Option :
NSInteger lineCount = 0;
CGSize textSize = CGSizeMake(yourLabel.frame.size.width, MAXFLOAT);
int rHeight = lroundf([yourLabel sizeThatFits:textSize].height);
int charSize = lroundf(yourLabel.font.lineHeight);
lineCount = rHeight/charSize;
NSLog(#"No of lines: %i",lineCount);
Here is a swift version of #Paresh solution:
func lines(label: UILabel) -> Int {
let textSize = CGSize(width: label.frame.size.width, height: CGFloat(Float.infinity))
let rHeight = lroundf(Float(label.sizeThatFits(textSize).height))
let charSize = lroundf(Float(label.font.lineHeight))
let lineCount = rHeight/charSize
return lineCount
}
EDIT: I don't know why, but the code is returning 2 more lines than the actual number of lines, for my solution, I just subtracted them before returning lineCount.
Swift 5.2
The main point to make it work for me was to call label.layoutIfNeeded() because I was using autoLayout, otherwise it doesnt work.
func actualNumberOfLines(label: UILabel) -> Int {
// You have to call layoutIfNeeded() if you are using autoLayout
label.layoutIfNeeded()
let myText = label.text! as NSString
let rect = CGSize(width: label.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: label.font as Any], context: nil)
return Int(ceil(CGFloat(labelSize.height) / label.font.lineHeight))
}
Credits to: https://gist.github.com/fuxingloh/ccf26bb68f4b8e6cfd02, which provided the solution in an older swift version, and for mentioning the importance of layoutIfNeeded().
The other answers here don't respect the numberOfLines property of UILabel when it is set to something other than 0.
Here's another option you can add to your category or subclass:
- (NSUInteger)lineCount
{
CGSize size = [self sizeThatFits:CGSizeMake(self.frame.size.width, CGFLOAT_MAX)];
return MAX((int)(size.height / self.font.lineHeight), 0);
}
Some notes:
I'm using this on a UILabel with attributed text, without ever actually setting the font property, and it's working fine. Obviously you would run into issues if you were using multiple fonts in your attributedText.
If you are subclassing UILabel to have custom edge insets (for example by overriding drawTextInRect:, which is a neat trick I found here), then you must remember to take those insets into account when calculating the size above. For example: CGSizeMake(self.frame.size.width - (self.insets.left + self.insets.right), CGFLOAT_MAX)
Here is the Swift3 Code
here you can define Int value and get the height of text size by using (MAXFLOAT) and using that height you can get the total height of UILabel and by deviding that total height by character size you can get the actual line count of UILabel.
var lineCount: Int = 0
var textSize = CGSize(width: CGFloat(yourLabel.frame.size.width), height: CGFloat(MAXFLOAT))
var rHeight: Int = lroundf(yourLabel.sizeThatFits(textSize).height)
var charSize: Int = lroundf(yourLabel.font.leading)
lineCount = rHeight / charSize
print("No of lines: \(lineCount)")
It seems that the official developer website mentions one solution Counting Lines of Text in Objc. However, it assumes you have a reference to a text view configured with a layout manager, text storage, and text container. Unfortunately, UILabel doesn't expose those to us, so we need create them with the same configuration as the UILabel.
I translated the Objc code to swift as following. It seems work well for me.
extension UILabel {
var actualNumberOfLines: Int {
let textStorage = NSTextStorage(attributedString: self.attributedText!)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: self.bounds.size)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = self.lineBreakMode
layoutManager.addTextContainer(textContainer)
let numberOfGlyphs = layoutManager.numberOfGlyphs
var numberOfLines = 0, index = 0, lineRange = NSMakeRange(0, 1)
while index < numberOfGlyphs {
layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
index = NSMaxRange(lineRange)
numberOfLines += 1
}
return numberOfLines
}
}
You can find the total number of line available in your custom label
Please check this code...
NSInteger numberOfLines = [self lineCountForText:#"YOUR TEXT"];
- (int)lineCountForText:(NSString *) text
{
UIFont *font = [UIFont systemFontOfSize: 15.0];
int width=Your_LabelWidht;
CGRect rect = [text boundingRectWithSize:CGSizeMake(width, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName : font} context:nil];
return ceil(rect.size.height / font.lineHeight);
}
Following up on #Prince's answer, I now implemented a category on UILabel as follows (note that I corrected some minor syntax mistakes in his answer that wouldn't let the code compile):
UILabel+Util.h
#import <UIKit/UIKit.h>
#interface UILabel (Util)
- (NSInteger)lineCount;
#end
UILabel+Util.,
#import "UILabel+Util.h"
#implementation UILabel (Util)
- (NSInteger)lineCount
{
// Calculate height text according to font
NSInteger lineCount = 0;
CGSize labelSize = (CGSize){self.frame.size.width, FLT_MAX};
CGRect requiredSize = [self.text boundingRectWithSize:labelSize options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName: self.font} context:nil];
// Calculate number of lines
int charSize = self.font.leading;
int rHeight = requiredSize.size.height;
lineCount = rHeight/charSize;
return lineCount;
}
#end
Xamarin iOS
label.Text = text;
var lineCount = 0;
var textSize = new CGSize(label.Frame.Size.Width, float.MaxValue);
var height = label.SizeThatFits(textSize).Height;
var fontHeight = label.Font.LineHeight;
lineCount = Convert.ToInt32(height / fontHeight);
Note that #kurt-j's answer will not always work. In some cases, you will have to manually provide the width of label. Since these cases exist, it is a good idea to have an optional width parameter, even if you don't end up using it.
Swift 4.2:
extension UILabel {
func calculateMaxLines(actualWidth: CGFloat?) -> Int {
var width = frame.size.width
if let actualWidth = actualWidth {
width = actualWidth
}
let maxSize = CGSize(width: width, height: CGFloat(Float.infinity))
let charSize = font.lineHeight
let text = (self.text ?? "") as NSString
let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
let linesRoundedUp = Int(ceil(textSize.height/charSize))
return linesRoundedUp
}
}
let l = UILabel()
l.numberOfLines = 0
l.layer.frame.size.width = self.view.frame.width - 40 /*padding(20 + 20)*/
l.font = UIFont(name: "BwModelica-Bold", size: 16.0)
l.text = "Random Any length Text!!"
let noOfLines = ceil(l.intrinsicContentSize.width / l.frame.size.width)
let lbl_height = noOfLines * l.intrinsicContentSize.height
This will be your Exact dynamic height of Label and Number of lines. Happy coding!!!
Xamarin.iOS
Thanks to the answers everyone provided above.
This gets number of visible lines.
public static int VisibleLineCount(this UILabel label)
{
var textSize = new CGSize(label.Frame.Size.Width, nfloat.MaxValue);
nfloat rHeight = label.SizeThatFits(textSize).Height;
nfloat charSize = label.Font.LineHeight;
return Convert.ToInt32(rHeight / charSize);
}
This gets actual number of lines the text will occupy on screen.
public static int LineCount(this UILabel label)
{
var maxSize = new CGSize(label.Frame.Size.Width, nfloat.MaxValue);
var charSize = label.Font.LineHeight;
var text = (label.Text ?? "").ToNSString();
var textSize = text.GetBoundingRect(maxSize, NSStringDrawingOptions.UsesLineFragmentOrigin, new UIStringAttributes() { Font = label.Font }, null);
return Convert.ToInt32(textSize.Height / charSize);
}
A helper method I find useful for my use case.
public static bool IsTextTruncated(this UILabel label)
{
if (label.Lines == 0)
{
return false;
}
return (label.LineCount() > label.Lines);
}
To get a more accurate line count:
Use font.lineHeight instead of font.pointSize
round() the line count after division
Swift 5.4
Refactor solution of Fernando Cardenas to UILabel extension
private extension UILabel {
var actualNumberOfLines: Int {
guard let text = self.text else {
return 0
}
layoutIfNeeded()
let rect = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
let labelSize = text.boundingRect(
with: rect,
options: .usesLineFragmentOrigin,
attributes: [NSAttributedString.Key.font: font as Any],
context: nil)
return Int(ceil(CGFloat(labelSize.height) / font.lineHeight))
}
}
⚠️ Nor lineHeight, nor leading is sufficient on its own.
The font.lineHeight is the height of the glyphs, it spans from descender (the bottom of the lowest glyph) to ascender (the top of the highest glyph). Also, note that lineHeight can be override for an attributed string. The font.leading is the additional (may be negative, though) space between (!) the lines (see docs for yourself). If you use the system fonts, you get different leading values for almost every point size.
So e.g. the height of a label with 5 lines is consist of 5 lineHeight and 4 leading (yet the leading value is often small enough to make the above solutions work up until a point where you start to work with a multitude of lines.
So the correct (pseudo) formula should be:
(frame.size.height + font.leading) / (font.lineHeight + font.leading)
Also also, if the attributed string has an attachment that is too big (higher than the ascender of the font), then it also alters the line height for that row.

Layout subviews not working properly

I have troubles with a custom view that Im designing.
Its essentially a table that display 12 labels, where the upper left label and the lower left label has to be width*5 of the other views. I have already added the views and adjusted the frame in layout subviews, but the labels does not appear in the view (already checked with the new views debugger of Xcode
override func layoutSubviews() {
super.layoutSubviews()
let width = self.frame.size.width
let height = self.frame.size.height
let normalWidth = width/10
let normalHeight = height/2
var currentOrigin = CGPoint(x: 0, y: 0)
let nameSize = CGSize(width: normalWidth * 5 - 3, height: normalHeight)
labels[0][0].frame = CGRect(origin: currentOrigin, size: nameSize)
currentOrigin.x += normalWidth
for j in labels[0]{
j.frame = CGRect(origin: currentOrigin, size: CGSize(width: normalWidth - 3, height: normalHeight))
currentOrigin.x += normalWidth
}
currentOrigin.y = normalHeight
currentOrigin.x = 0
labels[1][0].frame = CGRect(origin: currentOrigin, size: nameSize)
for j in labels[1]{
j.frame = CGRect(origin: currentOrigin, size: CGSize(width: normalWidth - 3, height: normalHeight))
currentOrigin.x += normalWidth
}
}
And this is the constructor that Im using. According to the debugger the views are in the superview but they are not visible
init(frame: CGRect) {
labels = Array(count:2, repeatedValue:Array(count:6, repeatedValue: UILabel() ))
super.init(frame: frame)
for i in 0..labels.count{
for j in 0..labels[i].count{
labels[i][j] = UILabel()
labels[i][j].font = currentFont
labels[i][j].adjustsFontSizeToFitWidth = true
labels[i][j].textAlignment = NSTextAlignment.Center
labels[i][j].text = "HOLA MUNDO"
addSubview(labels[i][j])
}
}
for i in 0..labels.count{
if let k = delegate?{
labels[i][0].text = k.name(i+1)
}
}
for i in 0..labels.count{
for j in 1..labels[i].count{
labels[i][j].text = "0"
}
}
}
In case someone has some similar troubles here is the solution that I finally found
labels = Array(count:2, repeatedValue:Array(count:6, repeatedValue: UILabel() ))
This line generates 2 arrays of UILabels, but all items of the arrays point to the same instance of UILabel. also:
labels[0] === labels[1] //They will point to the same instance
The other mistake was iterating in
for i in 0..labels.count{
if let k = delegate?{
labels[i][0].text = k.name(i+1)
}
}
The correct thing was to iterate from 1 to labels.count as the first label had to have a different size.
The correct form to instanciate the arrays is the following:
for i in 0..2{
labels.append([UILabel]())
for j in 0..6{
labels[i].append(UILabel())
labels[i][j].font = currentFont
labels[i][j].adjustsFontSizeToFitWidth = true
labels[i][j].textAlignment = NSTextAlignment.Center
labels[i][j].text = "HOLA MUNDO"
addSubview(labels[i][j])
}
Hope it help you to avoid this bug. It was really hard to find.

Resources