How to calculate UILabel height dynamically? - iphone

I want to calculate number of lines and height of UILabel dynamically from given text for same.

Try this
// UILabel *myLabel;
CGSize labelSize = [myLabel.text sizeWithFont:myLabel.font
constrainedToSize:myLabel.frame.size
lineBreakMode:NSLineBreakByWordWrapping];
CGFloat labelHeight = labelSize.height;
int lines = [myLabel.text sizeWithFont:myLabel.font
constrainedToSize:myLabel.frame.size
lineBreakMode:NSLineBreakByWordWrapping].height/16;
// '16' is font size
or
int lines = labelHeight/16;
NSLog(#"lines count : %i \n\n",lines);
or
int lines = [myLabel.text sizeWithFont:myLabel.font
constrainedToSize:myLabel.frame.size
lineBreakMode:UILineBreakModeWordWrap].height /myLabel.font.pointSize; //fetching font size from font
By Using Categories, Just Create the category class named as
UILabel+UILabelDynamicHeight.h
UILabel+UILabelDynamicHeight.m
No more tension about the height calculation. Please review the below implementation.
Updates for iOS7 & Above,iOS 7 below : Dynamically calculate the UILabel height
#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)
#define iOS7_0 #"7.0"
UILabel+UILabelDynamicHeight.h
#import <UIKit/UIKit.h>
#interface UILabel (UILabelDynamicHeight)
#pragma mark - Calculate the size the Multi line Label
/*====================================================================*/
/* Calculate the size of the Multi line Label */
/*====================================================================*/
/**
* Returns the size of the Label
*
* #param aLabel To be used to calculte the height
*
* #return size of the Label
*/
-(CGSize)sizeOfMultiLineLabel;
#end
UILabel+UILabelDynamicHeight.m
#import "UILabel+UILabelDynamicHeight.h"
#implementation UILabel (UILabelDynamicHeight)
#pragma mark - Calculate the size,bounds,frame of the Multi line Label
/*====================================================================*/
/* Calculate the size,bounds,frame of the Multi line Label */
/*====================================================================*/
/**
* Returns the size of the Label
*
* #param aLabel To be used to calculte the height
*
* #return size of the Label
*/
-(CGSize)sizeOfMultiLineLabel{
//Label text
NSString *aLabelTextString = [self text];
//Label font
UIFont *aLabelFont = [self font];
//Width of the Label
CGFloat aLabelSizeWidth = self.frame.size.width;
if (SYSTEM_VERSION_LESS_THAN(iOS7_0)) {
//version < 7.0
return [aLabelTextString sizeWithFont:aLabelFont
constrainedToSize:CGSizeMake(aLabelSizeWidth, MAXFLOAT)
lineBreakMode:NSLineBreakByWordWrapping];
}
else if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(iOS7_0)) {
//version >= 7.0
//Return the calculated size of the Label
return [aLabelTextString boundingRectWithSize:CGSizeMake(aLabelSizeWidth, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{
NSFontAttributeName : aLabelFont
}
context:nil].size;
}
return [self bounds].size;
}
#end

Calling -sizeToFit on UILabel instance will automatically resize it to fit text it displays, no calculating required. If you need the size, you can get it from label's frame property after that.
label.numberOfLines = 0; // allows label to have as many lines as needed
label.text = #"some long text";
[label sizeToFit];
NSLog(#"Label's frame is: %#", NSStringFromCGRect(label.frame));

To summarize, you can calculate the height of a label by using its string and calling boundingRectWithSize. You must provide the font as an attribute, and include .usesLineFragmentOrigin for multi-line labels.
let labelWidth = label.frame.width
let maxLabelSize = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude)
let actualLabelSize = label.text!.boundingRect(with: maxLabelSize, options: [.usesLineFragmentOrigin], attributes: [.font: label.font], context: nil)
let labelHeight = actualLabelSize.height(withWidth:labelWidth)
Some extensions to do just that:
Swift Version:
extension UILabel {
func textHeight(withWidth width: CGFloat) -> CGFloat {
guard let text = text else {
return 0
}
return text.height(withWidth: width, font: font)
}
func attributedTextHeight(withWidth width: CGFloat) -> CGFloat {
guard let attributedText = attributedText else {
return 0
}
return attributedText.height(withWidth: width)
}
}
extension String {
func height(withWidth width: CGFloat, font: UIFont) -> CGFloat {
let maxSize = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
let actualSize = self.boundingRect(with: maxSize, options: [.usesLineFragmentOrigin], attributes: [.font : font], context: nil)
return actualSize.height
}
}
extension NSAttributedString {
func height(withWidth width: CGFloat) -> CGFloat {
let maxSize = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
let actualSize = boundingRect(with: maxSize, options: [.usesLineFragmentOrigin], context: nil)
return actualSize.height
}
}
Objective-C Version:
UILabel+Utility.h
#import <UIKit/UIKit.h>
#interface UILabel (Utility)
- (CGFloat)textHeightForWidth:(CGFloat)width;
- (CGFloat)attributedTextHeightForWidth:(CGFloat)width;
#end
UILabel+Utility.m
#implementation NSString (Utility)
- (CGFloat)heightForWidth:(CGFloat)width font:(UIFont *)font {
CGSize maxSize = CGSizeMake(width, CGFLOAT_MAX);
CGSize actualSize = [self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName : font} context:nil].size;
return actualSize.height;
}
#end
#implementation NSAttributedString (Utility)
- (CGFloat)heightForWidth:(CGFloat)width {
CGSize maxSize = CGSizeMake(width, CGFLOAT_MAX);
CGSize actualSize = [self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
return actualSize.height;
}
#end
#implementation UILabel (Utility)
- (CGFloat)textHeightForWidth:(CGFloat)width {
return [self.text heightForWidth:width font:self.font];
}
- (CGFloat)attributedTextHeightForWidth:(CGFloat)width {
return [self.attributedText heightForWidth:width];
}
#end

The current solution has been deprecated as of iOS 7.
Here is an updated solution:
+ (CGFloat)heightOfCellWithIngredientLine:(NSString *)ingredientLine
withSuperviewWidth:(CGFloat)superviewWidth
{
CGFloat labelWidth = superviewWidth - 30.0f;
// use the known label width with a maximum height of 100 points
CGSize labelContraints = CGSizeMake(labelWidth, 100.0f);
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
CGRect labelRect = [ingredientLine boundingRectWithSize:labelContraints
options:NSStringDrawingUsesLineFragmentOrigin
attributes:nil
context:context];
// return the calculated required height of the cell considering the label
return labelRect.size.height;
}
The reason that my solution is set up like this is because I am using a UITableViewCell and resizing the cell dynamically relative to how much room the label will take up.

Without calling sizeToFit, you can do this all numerically with a very plug and play solution:
+ (CGFloat)heightForText:(NSString*)text font:(UIFont*)font withinWidth:(CGFloat)width {
CGSize size = [text sizeWithAttributes:#{NSFontAttributeName:font}];
CGFloat area = size.height * size.width;
CGFloat height = roundf(area / width);
return ceilf(height / font.lineHeight) * font.lineHeight;
}
I use it a lot for UITableViewCells that have dynamically allocated heights.
Solves the attributes problem as well #Salman Zaidi.

Copy & paste this method & used It like:
[lblText setFrame:CGRectMake(lblText.frame.origin.x, lblText.frame.origin.y, width, [self getLabelHeight:lblText])];
- (CGFloat)getLabelHeight:(UILabel*)label
{
CGSize constraint = CGSizeMake(label.frame.size.width, CGFLOAT_MAX);
CGSize size;
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
CGSize boundingBox = [label.text boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:label.font}
context:context].size;
size = CGSizeMake(ceil(boundingBox.width), ceil(boundingBox.height));
return size.height;
}

CGSize maxSize = CGSizeMake(lbl.frame.size.width, CGFLOAT_MAX);
CGSize requiredSize = [lbl sizeThatFits:maxSize];
CGFloat height=requiredSize.height

If you are using a UILabel with attributes, you can try the method textRect(forBounds:limitedToNumberOfLines).
This is my example:
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 30))
label.numberOfLines = 0
label.text = "Learn how to use RxSwift and RxCocoa to write applications that can react to changes in your underlying data without you telling it to do so."
let rectOfLabel = label.textRect(forBounds: CGRect(x: 0, y: 0, width: 100, height: CGFloat.greatestFiniteMagnitude), limitedToNumberOfLines: 0)
let rectOfLabelOneLine = label.textRect(forBounds: CGRect(x: 0, y: 0, width: 100, height: CGFloat.greatestFiniteMagnitude), limitedToNumberOfLines: 1)
let heightOfLabel = rectOfLabel.height
let heightOfLine = rectOfLabelOneLine.height
let numberOfLines = Int(heightOfLabel / heightOfLine)
And my results on the Playground:

To get height for the NSAttributedString use this function below. Where width - the width of your UILabel or UITextView
func getHeight(for attributedString: NSAttributedString, font: UIFont, width: CGFloat) -> CGFloat {
let textStorage = NSTextStorage(attributedString: attributedString)
let textContainter = NSTextContainer(size: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainter)
textStorage.addLayoutManager(layoutManager)
textStorage.addAttribute(NSAttributedString.Key.font, value: font, range: NSMakeRange(0, textStorage.length))
textContainter.lineFragmentPadding = 0.0
layoutManager.glyphRange(for: textContainter)
return layoutManager.usedRect(for: textContainter).size.height
}
To get height for String use this function, It is almost identical like the previous method:
func getHeight(for string: String, font: UIFont, width: CGFloat) -> CGFloat {
let textStorage = NSTextStorage(string: string)
let textContainter = NSTextContainer(size: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainter)
textStorage.addLayoutManager(layoutManager)
textStorage.addAttribute(NSAttributedString.Key.font, value: font, range: NSMakeRange(0, textStorage.length))
textContainter.lineFragmentPadding = 0.0
layoutManager.glyphRange(for: textContainter)
return layoutManager.usedRect(for: textContainter).size.height
}

You need to create an extension of String and call this method
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)
}
You must send the width of your label

In my case, I was using a fixed size header for each section but with a dynamically cell size in each header.
The cell's height, depends on the label's height.
Working with:
tableView.estimatedRowHeight = SomeNumber
tableView.rowHeight = UITableViewAutomaticDimension
Works but when using:
tableView.reloadSections(IndexSet(integer: sender.tag) , with: .automatic)
when a lot of headers are not collapsed, creates a lot of bugs such as header duplication (header type x below the same type) and weird animations when the framework reloads with animation, even when using with type .none (FYI, a fixed header height and cell height works).
The solution is making the use of heightForRowAt callback and calculate the height of the label by your self (plus the animation looks a lot better). Remember that the height is being called first.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
let object = dataDetailsController.getRowObject(forIndexPath: indexPath)
let label = UILabel(frame: tableView.frame)
let font = UIFont(name: "HelveticaNeue-Bold", size: 25)
label.text = object?.name
label.font = font
label.numberOfLines = 0
label.textAlignment = .center
label.sizeToFit()
let size = label.frame.height
return Float(size) == 0 ? 34 : size
}

This is the extension I use for calculating multiline UILabel heights, it's an adjusted snippet from a previous stack overflow post:
extension UILabel {
func estimatedHeight(forWidth: CGFloat, text: String, ofSize: CGFloat) -> CGFloat {
let size = CGSize(width: forWidth, height: CGFloat(MAXFLOAT))
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSFontAttributeName: UIFont.systemFont(ofSize: ofSize)]
let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height
return ceil(rectangleHeight)
}
}

To make UILabel fit the dynamic content you can use lines property in property inspector and set that as 0.
And you don't require to do any coding for this.
For more details you can check below demonstration video
https://www.youtube.com/watch?v=rVeDS_OGslU

if you want the label to take dynamic lines you may use this
label.numberOfLines = 0; // allows label to have as many lines as needed
label.text = #"some long text ";
[label sizeToFit];
NSLog(#"Label's frame is: %#", NSStringFromCGRect(label.frame));

Related

How to fix UITextView text content position in Swift

I have UIView class and in this class have UITextView for use as inputText
In textViewDidChange from row 2 through 5 with any inter, or adding text, I add the UITextView bounds.size.height as much as need.
The problem is, in rows 3 and 5, first line text does not appear.
To be roughly a row above its own space.
And is created from the bottom of the empty space, without scrolling.
But the text in the 4th row have exact position
class QuestionCell: UIView, UITextViewDelegate{
var paragraph = UITextView()
func setParagraph(){
let frame = CGRect(x: 50,
y: 0,
width: forground.width-55,
height: forground.height)
paragraph.frame = frame
paragraph.isEditable = true
paragraph.delegate = self
// forground is some UIView in this View
forground.addSubview(paragraph)
}
func textViewDidChange(_ textView: UITextView) {
paragraph.bounds.size.height = paragraph.paragraphHeight
}
}
extension UITextView{
var paragraphHeight: CGFloat{
let fixedWidth = self.width
let newSize = self.sizeThatFits(CGSize.init(width: fixedWidth,
height: CGFloat(MAXFLOAT)))
var newFrame = self.frame
newFrame.size = CGSize.init(width: CGFloat(fmaxf(Float(newSize.width),
Float(fixedWidth))),
height: newSize.height)
return newSize.height
}
}
During Creating you need to specify :
paragraph.isScrollEnabled = false
After creating, you may set it back.

How to get the height of a UILabel in Swift?

I am a beginner in Swift and I am trying to get the height of a label.
The label has multiple lines of text. I want to know the total height it occupies on the screen.
Swift 4 with extension
extension UILabel{
public var requiredHeight: CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.attributedText = attributedText
label.sizeToFit()
return label.frame.height
}
}
it's simple, just call
label.bounds.size.height
Updated for Swift 3
func estimatedHeightOfLabel(text: String) -> CGFloat {
let size = CGSize(width: view.frame.width - 16, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSFontAttributeName: UIFont.systemFont(ofSize: 10)]
let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height
return rectangleHeight
}
override func viewDidLoad() {
super.viewDidLoad()
guard let labelText = label1.text else { return }
let height = estimatedHeightOfLabel(text: labelText)
print(height)
}
Swift 5 ioS 13.2 tested 100%, best solution when the UILabel numberOfLines = 0
Note, result is rounded. Just remove ceil() if you don't want it.
If you want to get height -> give storyboard width of UILabel
If you want to get width -> give storyboard height of UILabel
let stringValue = ""//your label text
let width:CGFloat = 0//storybord width of UILabel
let height:CGFloat = 0//storyboard height of UILabel
let font = UIFont(name: "HelveticaNeue-Bold", size: 18)//font type and size
func getLableHeightRuntime() -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = stringValue.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.height)
}
func getLabelWidthRuntime() -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = stringValue.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.width)
}
#iajmeri43's answer Updated for Swift 5
func estimatedLabelHeight(text: String, width: CGFloat, font: UIFont) -> CGFloat {
let size = CGSize(width: width, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSAttributedString.Key.font: font]
let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height
return rectangleHeight
}
To use it:
// 1. get the text from the label
guard let theLabelsText = myLabel.text else { return }
// 2. get the width of the view the label is in for example a cell
// Here I'm just stating that the cell is the same exact width of whatever the collection's width is which is usually based on the width of the view that collectionView is in
let widthOfCell = self.collectionView.frame.width
// 3. get the font that your using for the label. For this example the label's font is UIFont.systemFont(ofSize: 17)
let theLabelsFont = UIFont.systemFont(ofSize: 17)
// 4. Plug the 3 values from above into the function
let totalLabelHeight = estimatedLabelHeight(text: theLabelsText, width: widthOfCell, font: theLabelsFont)
// 5. Print out the label's height with decimal values eg. 95.46875
print(totalLabelHeight)
// 6. as #slashburn suggested in the comments, use the ceil() function to round out the totalLabelHeight
let ceilHeight = ceil(totalLabelHeight)
// 7. Print out the ceilHeight rounded off eg. 95.0
print(ceilHeight)

How to draw vertical text in UILabel

I'm currently working on drawing vertical Chinese text in a label. Here's what I am trying to achieve, albeit with Chinese Characters:
I've been planning to draw each character, rotate each character 90 degrees to the left, then rotating the entire label via affine transformations to get the final result. However, it feels awfully complicated. Is there an easier way to draw the text without complicated CoreGraphics magic that I'm missing?
Well, You can do like below:
labelObject.numberOfLines = 0;
labelObject.lineBreakMode = NSLineBreakByCharWrapping;
and setFrame with -- height:100, width:20 It will work fine..
It works
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 30, 100)];
lbl.transform = CGAffineTransformMakeRotation((M_PI)/2);
Tried the method offered by Simha.IC but it didn't work well for me. Some characters are thinner than others and get placed two on a line. E.g.
W
ai
ti
n
g
The solution for me was to create a method that transforms the string itself into a multiline text by adding \n after each character. Here's the method:
- (NSString *)transformStringToVertical:(NSString *)originalString
{
NSMutableString *mutableString = [NSMutableString stringWithString:originalString];
NSRange stringRange = [mutableString rangeOfString:mutableString];
for (int i = 1; i < stringRange.length*2 - 2; i+=2)
{
[mutableString insertString:#"\n" atIndex:i];
}
return mutableString;
}
Then you just setup the label like this:
label.text = [self transformStringToVertical:myString];
CGRect labelFrame = label.frame;
labelFrame.size.width = label.font.pointSize;
labelFrame.size.height = label.font.lineHeight * myString.length;
label.frame = labelFrame;
Enjoy!
If you would like to rotate the whole label (including characters), you can do so as follows:
First add the QuartzCore library to your project.
Create a label:
UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 30.0)];
[label setText:#"Label Text"];
Rotate the label:
[label setTransform:CGAffineTransformMakeRotation(-M_PI / 2)];
Depending on how you'd like to position the label you may need to set the anchor point. This sets the point around which a rotation occurs. Eg:
[label.layer setAnchorPoint:CGPointMake(0.0, 1.0)];
This is another way to draw vertical text, by subclassing UILabel. But it is some kind different of what the question want.
Objective-C
#implementation MyVerticalLabel
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform transform = CGAffineTransformMakeRotation(-M_PI_2);
CGContextConcatCTM(context, transform);
CGContextTranslateCTM(context, -rect.size.height, 0);
CGRect newRect = CGRectApplyAffineTransform(rect, transform);
newRect.origin = CGPointZero;
NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
textStyle.lineBreakMode = self.lineBreakMode;
textStyle.alignment = self.textAlignment;
NSDictionary *attributeDict =
#{
NSFontAttributeName : self.font,
NSForegroundColorAttributeName : self.textColor,
NSParagraphStyleAttributeName : textStyle,
};
[self.text drawInRect:newRect withAttributes:attributeDict];
}
#end
A sample image is following:
Swift
It can put on the storyboard, and watch the result directly. Like the image, it's frame will contain the vertical text. And text attributes, like textAlignment, font, work well too.
#IBDesignable
class MyVerticalLabel: UILabel {
override func drawRect(rect: CGRect) {
guard let text = self.text else {
return
}
// Drawing code
let context = UIGraphicsGetCurrentContext()
let transform = CGAffineTransformMakeRotation( CGFloat(-M_PI_2))
CGContextConcatCTM(context, transform)
CGContextTranslateCTM(context, -rect.size.height, 0)
var newRect = CGRectApplyAffineTransform(rect, transform)
newRect.origin = CGPointZero
let textStyle = NSMutableParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
textStyle.lineBreakMode = self.lineBreakMode
textStyle.alignment = self.textAlignment
let attributeDict: [String:AnyObject] = [
NSFontAttributeName: self.font,
NSForegroundColorAttributeName: self.textColor,
NSParagraphStyleAttributeName: textStyle,
]
let nsStr = text as NSString
nsStr.drawInRect(newRect, withAttributes: attributeDict)
}
}
Swift 4
override func draw(_ rect: CGRect) {
guard let text = self.text else {
return
}
// Drawing code
if let context = UIGraphicsGetCurrentContext() {
let transform = CGAffineTransform( rotationAngle: CGFloat(-Double.pi/2))
context.concatenate(transform)
context.translateBy(x: -rect.size.height, y: 0)
var newRect = rect.applying(transform)
newRect.origin = CGPoint.zero
let textStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
textStyle.lineBreakMode = self.lineBreakMode
textStyle.alignment = self.textAlignment
let attributeDict: [NSAttributedStringKey: AnyObject] = [NSAttributedStringKey.font: self.font, NSAttributedStringKey.foregroundColor: self.textColor, NSAttributedStringKey.paragraphStyle: textStyle]
let nsStr = text as NSString
nsStr.draw(in: newRect, withAttributes: attributeDict)
}
}
Swift 5
More easy way with CGAffineTransform
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var verticalText: UILabel
override func viewDidLoad() {
verticalText.transform = CGAffineTransform(rotationAngle:CGFloat.pi/2)
}
}
import UIKit
class VerticalLabel : UILabel {
private var _text : String? = nil
override var text : String? {
get {
return _text
}
set {
self.numberOfLines = 0
self.textAlignment = .center
self.lineBreakMode = .byWordWrapping
_text = newValue
if let t = _text {
var s = ""
for c in t {
s += "\(c)\n"
}
super.text = s
}
}
}
}

Resize font size in UITextView

Is there a way to shrink the font-size in a UITextView if there is too much text? Similar to the UILabel?
The problem with the accepted answer is that you have to guess the number of characters (the string's length) needed to fill the field, and that differs from font to font. Something like this, a category on UITextView, should work.
#import "UITextView+Size.h"
#define kMaxFieldHeight 1000
#implementation UITextView (Size)
-(BOOL)sizeFontToFitMinSize:(float)aMinFontSize maxSize:(float)aMaxFontSize {
float fudgeFactor = 16.0;
float fontSize = aMaxFontSize;
self.font = [self.font fontWithSize:fontSize];
CGSize tallerSize = CGSizeMake(self.frame.size.width-fudgeFactor,kMaxFieldHeight);
CGSize stringSize = [self.text sizeWithFont:self.font constrainedToSize:tallerSize lineBreakMode:UILineBreakModeWordWrap];
while (stringSize.height >= self.frame.size.height) {
if (fontSize <= aMinFontSize) // it just won't fit, ever
return NO;
fontSize -= 1.0;
self.font = [self.font fontWithSize:fontSize];
tallerSize = CGSizeMake(self.frame.size.width-fudgeFactor,kMaxFieldHeight);
stringSize = [self.text sizeWithFont:self.font constrainedToSize:tallerSize lineBreakMode:UILineBreakModeWordWrap];
}
return YES;
}
#end
Try this:
NSInteger lengthThreshold = 200;
if( [ textView.text length ] > lengthThreshold ) {
NSInteger newSize = ... //calculate new size based on length
[ textView setFont: [ UIFont systemFontOfSize: newSize ]];
}
Swift 4 implementation inspired by #Jane Sales's answer.
When calculating available width and height we must also take into consideration possible vertical and horizontal margins (textContainerInset and textContainer.lineFragmentPadding).
Here's a better explanation of how margins work on UITextView: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextUILayer/Tasks/SetTextMargins.html
If the text view can resize, then we must also force a layout so we can calculate the font size based on biggest possible text view size. In this case only height is considered (layouts only if required text height is bigger than original available height).
import UIKit
extension UITextView {
func adjustFontToFitText(minimumScale: CGFloat) {
guard let font = font else {
return
}
let scale = max(0.0, min(1.0, minimumScale))
let minimumFontSize = font.pointSize * scale
adjustFontToFitText(minimumFontSize: minimumFontSize)
}
func adjustFontToFitText(minimumFontSize: CGFloat) {
guard let font = font, minimumFontSize > 0.0 else {
return
}
let minimumSize = floor(minimumFontSize)
var fontSize = font.pointSize
let availableWidth = bounds.width - (textContainerInset.left + textContainerInset.right) - (2 * textContainer.lineFragmentPadding)
var availableHeight = bounds.height - (textContainerInset.top + textContainerInset.bottom)
let boundingSize = CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)
var height = text.boundingRect(with: boundingSize, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil).height
if height > availableHeight {
// If text view can vertically resize than we want to get the maximum possible height
sizeToFit()
layoutIfNeeded()
availableHeight = bounds.height - (textContainerInset.top + textContainerInset.bottom)
}
while height >= availableHeight {
guard fontSize > minimumSize else {
break
}
fontSize -= 1.0
let newFont = font.withSize(fontSize)
height = text.boundingRect(with: boundingSize, options: .usesLineFragmentOrigin, attributes: [.font: newFont], context: nil).height
}
self.font = font.withSize(fontSize)
}
}

How do I make UITableViewCell's ImageView a fixed size even when the image is smaller

I have a bunch of images I am using for cell's image views, they are all no bigger than 50x50. e.g. 40x50, 50x32, 20x37 .....
When I load the table view, the text doesn't line up because the width of the images varies. Also I would like small images to appear in the centre as opposed to on the left.
Here is the code I am trying inside my 'cellForRowAtIndexPath' method
cell.imageView.autoresizingMask = ( UIViewAutoresizingNone );
cell.imageView.autoresizesSubviews = NO;
cell.imageView.contentMode = UIViewContentModeCenter;
cell.imageView.bounds = CGRectMake(0, 0, 50, 50);
cell.imageView.frame = CGRectMake(0, 0, 50, 50);
cell.imageView.image = [UIImage imageWithData: imageData];
As you can see I have tried a few things, but none of them work.
It's not necessary to rewrite everything. I recommend doing this instead:
Post this inside your .m file of your custom cell.
- (void)layoutSubviews {
[super layoutSubviews];
self.imageView.frame = CGRectMake(0,0,32,32);
}
This should do the trick nicely. :]
For those of you who don't have a subclass of UITableViewCell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
[...]
CGSize itemSize = CGSizeMake(40, 40);
UIGraphicsBeginImageContextWithOptions(itemSize, NO, UIScreen.mainScreen.scale);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[cell.imageView.image drawInRect:imageRect];
cell.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[...]
return cell;
}
The code above sets the size to be 40x40.
Swift 2
let itemSize = CGSizeMake(25, 25);
UIGraphicsBeginImageContextWithOptions(itemSize, false, UIScreen.mainScreen().scale);
let imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
cell.imageView?.image!.drawInRect(imageRect)
cell.imageView?.image! = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Or you can use another(not tested) approach suggested by #Tommy:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
[...]
CGSize itemSize = CGSizeMake(40, 40);
UIGraphicsBeginImageContextWithOptions(itemSize, NO, 0.0)
[...]
return cell;
}
Swift 3+
let itemSize = CGSize.init(width: 25, height: 25)
UIGraphicsBeginImageContextWithOptions(itemSize, false, UIScreen.main.scale);
let imageRect = CGRect.init(origin: CGPoint.zero, size: itemSize)
cell?.imageView?.image!.draw(in: imageRect)
cell?.imageView?.image! = UIGraphicsGetImageFromCurrentImageContext()!;
UIGraphicsEndImageContext();
The code above is the Swift 3+ version of the above.
Here's how i did it. This technique takes care of moving the text and detail text labels appropriately to the left:
#interface SizableImageCell : UITableViewCell {}
#end
#implementation SizableImageCell
- (void)layoutSubviews {
[super layoutSubviews];
float desiredWidth = 80;
float w=self.imageView.frame.size.width;
if (w>desiredWidth) {
float widthSub = w - desiredWidth;
self.imageView.frame = CGRectMake(self.imageView.frame.origin.x,self.imageView.frame.origin.y,desiredWidth,self.imageView.frame.size.height);
self.textLabel.frame = CGRectMake(self.textLabel.frame.origin.x-widthSub,self.textLabel.frame.origin.y,self.textLabel.frame.size.width+widthSub,self.textLabel.frame.size.height);
self.detailTextLabel.frame = CGRectMake(self.detailTextLabel.frame.origin.x-widthSub,self.detailTextLabel.frame.origin.y,self.detailTextLabel.frame.size.width+widthSub,self.detailTextLabel.frame.size.height);
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
}
}
#end
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[SizableImageCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
cell.textLabel.text = ...
cell.detailTextLabel.text = ...
cell.imageView.image = ...
return cell;
}
image view add as a sub view to the tableview cell
UIImageView *imgView=[[UIImageView alloc] initWithFrame:CGRectMake(20, 5, 90, 70)];
imgView.backgroundColor=[UIColor clearColor];
[imgView.layer setCornerRadius:8.0f];
[imgView.layer setMasksToBounds:YES];
[imgView setImage:[UIImage imageWithData: imageData]];
[cell.contentView addSubview:imgView];
The whole cell doesn't need to be remade. You could use the indentationLevel and indentationWidth property of tableViewCells to shift the content of your cell. Then you add your custom imageView to the left side of the cell.
Better create an image view and add it as a sub view to the cell.Then you can get the desired frame size.
A Simply Swift,
Step 1: Create One SubClass of UITableViewCell
Step 2: Add this method to SubClass of UITableViewCell
override func layoutSubviews() {
super.layoutSubviews()
self.imageView?.frame = CGRectMake(0, 0, 10, 10)
}
Step 3: Create cell object using that SubClass in cellForRowAtIndexPath,
Ex: let customCell:CustomCell = CustomCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
Step 4: Enjoy
UIImage *image = cell.imageView.image;
UIGraphicsBeginImageContext(CGSizeMake(35,35));
// draw scaled image into thumbnail context
[image drawInRect:CGRectMake(5, 5, 35, 35)]; //
UIImage *newThumbnail = UIGraphicsGetImageFromCurrentImageContext();
// pop the context
UIGraphicsEndImageContext();
if(newThumbnail == nil)
{
NSLog(#"could not scale image");
cell.imageView.image = image;
}
else
{
cell.imageView.image = newThumbnail;
}
I've created an extension using #GermanAttanasio 's answer. It provides a method to resize an image to a desired size, and another method to do the same while adding a transparent margin to the image (this can be useful for table views where you want the image to have a margin as well).
import UIKit
extension UIImage {
/// Resizes an image to the specified size.
///
/// - Parameters:
/// - size: the size we desire to resize the image to.
///
/// - Returns: the resized image.
///
func imageWithSize(size: CGSize) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.mainScreen().scale);
let rect = CGRectMake(0.0, 0.0, size.width, size.height);
drawInRect(rect)
let resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultingImage
}
/// Resizes an image to the specified size and adds an extra transparent margin at all sides of
/// the image.
///
/// - Parameters:
/// - size: the size we desire to resize the image to.
/// - extraMargin: the extra transparent margin to add to all sides of the image.
///
/// - Returns: the resized image. The extra margin is added to the input image size. So that
/// the final image's size will be equal to:
/// `CGSize(width: size.width + extraMargin * 2, height: size.height + extraMargin * 2)`
///
func imageWithSize(size: CGSize, extraMargin: CGFloat) -> UIImage {
let imageSize = CGSize(width: size.width + extraMargin * 2, height: size.height + extraMargin * 2)
UIGraphicsBeginImageContextWithOptions(imageSize, false, UIScreen.mainScreen().scale);
let drawingRect = CGRect(x: extraMargin, y: extraMargin, width: size.width, height: size.height)
drawInRect(drawingRect)
let resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultingImage
}
}
This worked for me in swift:
Create a subclass of UITableViewCell (make sure you link up your cell in the storyboard)
class MyTableCell:UITableViewCell{
override func layoutSubviews() {
super.layoutSubviews()
if(self.imageView?.image != nil){
let cellFrame = self.frame
let textLabelFrame = self.textLabel?.frame
let detailTextLabelFrame = self.detailTextLabel?.frame
let imageViewFrame = self.imageView?.frame
self.imageView?.contentMode = .ScaleAspectFill
self.imageView?.clipsToBounds = true
self.imageView?.frame = CGRectMake((imageViewFrame?.origin.x)!,(imageViewFrame?.origin.y)! + 1,40,40)
self.textLabel!.frame = CGRectMake(50 + (imageViewFrame?.origin.x)! , (textLabelFrame?.origin.y)!, cellFrame.width-(70 + (imageViewFrame?.origin.x)!), textLabelFrame!.height)
self.detailTextLabel!.frame = CGRectMake(50 + (imageViewFrame?.origin.x)!, (detailTextLabelFrame?.origin.y)!, cellFrame.width-(70 + (imageViewFrame?.origin.x)!), detailTextLabelFrame!.height)
}
}
}
In cellForRowAtIndexPath , dequeue the cell as your new cell type:
let cell = tableView.dequeueReusableCellWithIdentifier("MyCell", forIndexPath: indexPath) as! MyTableCell
Obviously change number values to suit your layout
Here is #germanattanasio 's working method, written for Swift 3
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.imageView?.image = myImage
let itemSize = CGSize(width:42.0, height:42.0)
UIGraphicsBeginImageContextWithOptions(itemSize, false, 0.0)
let imageRect = CGRect(x:0.0, y:0.0, width:itemSize.width, height:itemSize.height)
cell.imageView?.image!.draw(in:imageRect)
cell.imageView?.image! = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
}
If you use cell.imageView?.translatesAutoresizingMaskIntoConstraints = false you can set constraints on the imageView. Here's a working example I used in a project. I avoided subclassing and didn't need to create storyboard with prototype cells but did take me quite a while to get running, so probably best to only use if there isn't a simpler or more concise way available to you.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: String(describing: ChangesRequiringApprovalTableViewController.self))
let record = records[indexPath.row]
cell.textLabel?.text = "Title text"
if let thumb = record["thumbnail"] as? CKAsset, let image = UIImage(contentsOfFile: thumb.fileURL.path) {
cell.imageView?.contentMode = .scaleAspectFill
cell.imageView?.image = image
cell.imageView?.translatesAutoresizingMaskIntoConstraints = false
cell.imageView?.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor).isActive = true
cell.imageView?.widthAnchor.constraint(equalToConstant: 80).rowHeight).isActive = true
cell.imageView?.heightAnchor.constraint(equalToConstant: 80).isActive = true
if let textLabel = cell.textLabel {
let margins = cell.contentView.layoutMarginsGuide
textLabel.translatesAutoresizingMaskIntoConstraints = false
cell.imageView?.trailingAnchor.constraint(equalTo: textLabel.leadingAnchor, constant: -8).isActive = true
textLabel.topAnchor.constraint(equalTo: margins.topAnchor).isActive = true
textLabel.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
let bottomConstraint = textLabel.bottomAnchor.constraint(equalTo: margins.bottomAnchor)
bottomConstraint.priority = UILayoutPriorityDefaultHigh
bottomConstraint.isActive = true
if let description = cell.detailTextLabel {
description.translatesAutoresizingMaskIntoConstraints = false
description.bottomAnchor.constraint(equalTo: margins.bottomAnchor).isActive = true
description.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
cell.imageView?.trailingAnchor.constraint(equalTo: description.leadingAnchor, constant: -8).isActive = true
textLabel.bottomAnchor.constraint(equalTo: description.topAnchor).isActive = true
}
}
cell.imageView?.clipsToBounds = true
}
cell.detailTextLabel?.text = "Detail Text"
return cell
}
I had the same problem. Thank you to everyone else who answered - I was able to get a solution together using parts of several of these answers.
My solution is using swift 5
The problem that we are trying to solve is that we may have images with different aspect ratios in our TableViewCells but we want them to render with consistent widths. The images should, of course, render with no distortion and fill the entire space. In my case, I was fine with some "cropping" of tall, skinny images, so I used the content mode .scaleAspectFill
To do this, I created a custom subclass of UITableViewCell. In my case, I named it StoryTableViewCell. The entire class is pasted below, with comments inline.
This approach worked for me when also using a custom Accessory View and long text labels. Here's an image of the final result:
Rendered Table View with consistent image width
class StoryTableViewCell: UITableViewCell {
override func layoutSubviews() {
super.layoutSubviews()
// ==== Step 1 ====
// ensure we have an image
guard let imageView = self.imageView else {return}
// create a variable for the desired image width
let desiredWidth:CGFloat = 70;
// get the width of the image currently rendered in the cell
let currentImageWidth = imageView.frame.size.width;
// grab the width of the entire cell's contents, to be used later
let contentWidth = self.contentView.bounds.width
// ==== Step 2 ====
// only update the image's width if the current image width isn't what we want it to be
if (currentImageWidth != desiredWidth) {
//calculate the difference in width
let widthDifference = currentImageWidth - desiredWidth;
// ==== Step 3 ====
// Update the image's frame,
// maintaining it's original x and y values, but with a new width
self.imageView?.frame = CGRect(imageView.frame.origin.x,
imageView.frame.origin.y,
desiredWidth,
imageView.frame.size.height);
// ==== Step 4 ====
// If there is a texst label, we want to move it's x position to
// ensure it isn't overlapping with the image, and that it has proper spacing with the image
if let textLabel = self.textLabel
{
let originalFrame = self.textLabel?.frame
// the new X position for the label is just the original position,
// minus the difference in the image's width
let newX = textLabel.frame.origin.x - widthDifference
self.textLabel?.frame = CGRect(newX,
textLabel.frame.origin.y,
contentWidth - newX,
textLabel.frame.size.height);
print("textLabel info: Original =\(originalFrame!)", "updated=\(self.textLabel!.frame)")
}
// ==== Step 4 ====
// If there is a detail text label, do the same as step 3
if let detailTextLabel = self.detailTextLabel {
let originalFrame = self.detailTextLabel?.frame
let newX = detailTextLabel.frame.origin.x-widthDifference
self.detailTextLabel?.frame = CGRect(x: newX,
y: detailTextLabel.frame.origin.y,
width: contentWidth - newX,
height: detailTextLabel.frame.size.height);
print("detailLabel info: Original =\(originalFrame!)", "updated=\(self.detailTextLabel!.frame)")
}
// ==== Step 5 ====
// Set the image's content modoe to scaleAspectFill so it takes up the entire view, but doesn't get distorted
self.imageView?.contentMode = .scaleAspectFill;
}
}
}
The regular UITableViewCell works well to position things but the cell.imageView doesn't seem to behave like you want it to. I found that it's simple enough to get the UITableViewCell to lay out properly by first giving the cell.imageView a properly sized image like
// Putting in a blank image to make sure text always pushed to the side.
UIGraphicsBeginImageContextWithOptions(CGSizeMake(kGroupImageDimension, kGroupImageDimension), NO, 0.0);
UIImage *blank = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
cell.imageView.image = blank;
Then you can just connect up your own properly working UIImageView with
// The cell.imageView increases in size to accomodate the image given it.
// We don't want this behaviour so we just attached a view on top of cell.imageView.
// This gives us the positioning of the cell.imageView without the sizing
// behaviour.
UIImageView *anImageView = nil;
NSArray *subviews = [cell.imageView subviews];
if ([subviews count] == 0)
{
anImageView = [[UIImageView alloc] init];
anImageView.translatesAutoresizingMaskIntoConstraints = NO;
[cell.imageView addSubview:anImageView];
NSLayoutConstraint *aConstraint = [NSLayoutConstraint constraintWithItem:anImageView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:cell.imageView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
[cell.imageView addConstraint:aConstraint];
aConstraint = [NSLayoutConstraint constraintWithItem:anImageView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:cell.imageView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0];
[cell.imageView addConstraint:aConstraint];
aConstraint = [NSLayoutConstraint constraintWithItem:anImageView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:kGroupImageDimension];
[cell.imageView addConstraint:aConstraint];
aConstraint = [NSLayoutConstraint constraintWithItem:anImageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:kGroupImageDimension];
[cell.imageView addConstraint:aConstraint];
}
else
{
anImageView = [subviews firstObject];
}
Set the image on anImageView and it will do what you expect a UIImageView to do. Be the size you want it regardless of the image you give it. This should go in tableView:cellForRowAtIndexPath:
This solution essentially draws the image as 'aspect fit' within the given rect.
CGSize itemSize = CGSizeMake(80, 80);
UIGraphicsBeginImageContextWithOptions(itemSize, NO, UIScreen.mainScreen.scale);
UIImage *image = cell.imageView.image;
CGRect imageRect;
if(image.size.height > image.size.width) {
CGFloat width = itemSize.height * image.size.width / image.size.height;
imageRect = CGRectMake((itemSize.width - width) / 2, 0, width, itemSize.height);
} else {
CGFloat height = itemSize.width * image.size.height / image.size.width;
imageRect = CGRectMake(0, (itemSize.height - height) / 2, itemSize.width, height);
}
[cell.imageView.image drawInRect:imageRect];
cell.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The solution we ended up with is similar to many of the others. But to get the correct position of the separator we had to set it before calling super.layoutSubviews(). Simplified example:
class ImageTableViewCell: UITableViewCell {
override func layoutSubviews() {
separatorInset.left = 70
super.layoutSubviews()
imageView?.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
textLabel?.frame = CGRect(x: 70, y: 0, width: 200, height: 50)
}
}