UILabel as tableViewCell's accessoryView is shifted up - ios

I am creating a normal UILabel and setting it as tableViewCell's accessoryView. My understanding is, accessoryView stays vertically centre aligned inside cell. But that is not happening. As I decrease the label text's font, the accessory view moves more upwards. Button works just fine. Problem with UILabel.
Here is my code :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
cell.textLabel?.text = "Hello"
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 42, height: 21))
label.backgroundColor = UIColor.red
label.font = UIFont(name: "Helvetica Neue", size: 12)
label.highlightedTextColor = UIColor.white
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.green
label.textAlignment = .right;
label.clipsToBounds = true
label.autoresizesSubviews = true
label.contentMode = .left
label.text = "123";
cell.accessoryView = label
return cell
}

As #Faizyy has noticed, there's an issue with iOS 13, however providing small height is not always an option. My approach was to put UILabel into UIView of the same size as UILabel and use that container view as accessory view. This has solved alignment issues for me. Here's an example:
cell.accessoryView = [self getBadge:unread];
-(UIView *)getBadge:(int)count {
CGFloat size = 28;
CGFloat digits = [[#(count) stringValue] length];
CGFloat width = MAX(size, 0.7 * size * digits);
UILabel *badge = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, width, size)];
badge.text = [#(count) stringValue];
badge.layer.cornerRadius = size / 2;
badge.layer.masksToBounds = YES;
badge.textAlignment = NSTextAlignmentCenter;
badge.font = [UIFont fontWithName:#"AvenirNext-Medium" size:16];
badge.textColor = UIColor.whiteColor;
badge.backgroundColor = VSColor.blue;
UIView *badgeHolder = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, size)];
badgeHolder.backgroundColor = UIColor.clearColor;
[badgeHolder addSubview:badge];
return badgeHolder;
}

If you are working pre SwiftUI, you should consider using auto layout to center the UILabel to the cell to prevent it from moving up. If you're using SwiftUI, you have to use SwiftUI Layout System. Hope that helps.

I found a solution. The frame that I was giving to UILabel was CGRect(x: 0, y: 0, width: 42, height: 21). If I give the height same as the text size i.e. 12.0, The label becomes centre aligned :).
This seems like an iOS 13 issue. Not seen in iOS 12 devices.
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 42, height: 12))

Related

Not getting correct height of UILabel in Objective-c

I have string with multiple new line characters like below.
"sdfsdghhfdgdfgfdgdfghjgf
sdfsdfsdfsdfdsfdsfdsgdfgfdhghgfhfgjhf
sdfdsfdsfdsfdsfdsfdsfhhg
sdfsdfdfsd
sdfdsfdsfdsfdsfdfdsfsdf"
Now I am calculating height of label to accommodate above string. Below is my code for it.
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
label.font = font;
label.text = labelText;
float width = label.frame.size.width;
CGSize jobDescHeightSize = [label sizeThatFits:CGSizeMake(width, CGFLOAT_MAX)];
It gives me correct height in larger device i.e. iPhone XR, iPhone 11 but it doesn't give me correct size of UILabel in iPhone 6s, iPhone 8.
The extension which solved my issue once
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
}
}
Original Answer

UILabel and UITextView line breaks don't match

I have a UILabel and a UITextView, where my intention is for them both to display the same text, and for the appearance to be the same. I'm having some issue with that right now. I've set up a demonstration in a Swift Playground:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.frame = CGRect(x: 100, y: 100, width: 247, height: 39)
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 16)
label.text = "I’m on my way back to London today"
label.textColor = .black
label.backgroundColor = .red
label.lineBreakMode = .byWordWrapping
view.addSubview(label)
let textView = UITextView()
textView.frame = CGRect(x: 100, y: 200, width: 247, height: 39)
textView.font = label.font
textView.text = label.text
textView.textColor = label.textColor
textView.backgroundColor = label.backgroundColor
textView.textContainer.lineBreakMode = label.lineBreakMode
textView.textContainerInset = UIEdgeInsets.zero
textView.textContainer.lineFragmentPadding = 0
view.addSubview(textView)
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
As you can see, they're both setup in the same way, but the linebreak occurs in a different place on the UITextView. I've set the lineBreakMode to the same value for both. I've also removed the textContainerInsets and lineFragmentPadding on the UITextView, so the text is positioned with the same padding within both views.
It was a change to UILabel in IOS 11 to fix orphaned words. No way to shut it off although I wish there was. The only way to do it would be to use a non editable/nonscrollable textview or a CATextLayer. To get the sizing attributes of a UILabel I am afraid you have to do that manually.
See this as the problem was similar. Although not in that answer I did find that UITextView wraps the old way. I am using a UITextView now and I manually adjust the font size in textDidChange. I hold the original font so I can always resize.

Programmatically adding constraints to a UITableView header

I have a header on my tableview, which has an image and a label. I want to center the UIImage and have the label pinned underneath it. This is my code currently:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerLabel = UILabel()
let logoView = UIImageView()
headerLabel.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 21)
headerLabel.text = self.navigationItem.title
headerLabel.textColor = UIColor.white
headerLabel.backgroundColor = UIColor.clear
headerLabel.textAlignment = NSTextAlignment.center
logoView.frame = CGRect(x: 0, y: 0, width: 90, height: 90)
let logo: UIImage = UIImage(named: self.navigationItem.title!)!
logoView.image = logo
logoView.contentMode = .scaleAspectFill
view.addSubview(headerLabel)
view.addSubview(logoView)
return topView
}
This puts the label centered on the top of the header, and the logo in the top left corner. How can I add constraints (programmatically, no storyboard) to center the image and pink the label below it? I've been using programmatic constraints quite a bit (i.e. something.leftAnchor.constraint(equalTo....) but I'm not sure how to apply it in this situation as it's my first use of a header.
I want to center the UIImage and have the label pinned underneath it.
This can be achieved by making the framing logic of each subview dependent of the neighbouring views. Whilst doing this programmatically, one has to be extra careful about the geometric calculations involved.
This snippet should do it:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let logoView = UIImageView()
logoView.frame = CGRect(x: tableView.center.x - 90/2, y: 0, width: 90, height: 90)
let logo: UIImage = UIImage(named: self.navigationItem.title!)!
logoView.image = logo
logoView.contentMode = .scaleAspectFill
view.addSubview(logoView)
let headerLabel = UILabel()
headerLabel.frame = CGRect(x: 0, y: logoView.frame.size.height, width: view.frame.width, height: 21)
headerLabel.text = self.navigationItem.title
headerLabel.textColor = UIColor.white
headerLabel.backgroundColor = UIColor.clear
headerLabel.textAlignment = NSTextAlignment.center
view.addSubview(headerLabel)
return topView
}
You can use that code to set default paddings.
if #available(iOS 15.0, *) { tableView.sectionHeaderTopPadding = 0 }

Resize UITableViewCell Height based on UILabel Content

I've been looking into this for over a day now, and I can honestly say I am completely stumped by why this is not working as I would expect it to.
I'm trying to have a UITableViewCell expand when selected to the correct size based on the UILabel within it. I have used the following code to determine the required size for the UILabel:
extension UILabel {
func requiredHeight() -> CGFloat{
let label:UILabel = UILabel(frame: CGRectMake(0, 0, self.frame.width, CGFloat.max))
label.text = self.text
label.numberOfLines = 0
label.font = self.font
label.sizeToFit()
print("Final Size - \(label.frame.height)")
return label.frame.height + 10
}
}
My issue is, despite this size - when the Cell is resized within the 'heightForRowAtIndexPath' method - it is still not the correct size and the string is being truncated, this can be seen in the below image.
To note - I gather the required size of the cell as soon as the view has loaded and text has been populated into the UILabel.
requiredHeight = overviewLabel.requiredHeight()
if requiredHeight > overviewCell.frame.height {
expander.hidden = false
} else {
expander.hidden = true
}
Any advice on how this could be fixed will be greatly appreciated.
Try this snippet. Just provide exact name of the font and size
Swift 3.x
func requiredHeight() -> CGFloat{
let font = UIFont(name: "Helvetica", size: 16.0)
let label:UILabel = UILabel(frame: CGRect(x:0, y:0, width:200, height:CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = self.text
label.sizeToFit()
return label.frame.height + //Add some space as a part of your bottom and top constraint
}
Swift 2.2
func requiredHeight() -> CGFloat{
let font = UIFont(name: "Helvetica", size: 16.0)
let label:UILabel = UILabel(frame: CGRectMake(0, 0, 200, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.font = font
label.text = self.text
label.sizeToFit()
return label.frame.height + //Add some space as a part of your bottom and top constraint
}
Suppose your label has top and bottom constraint as 5 and 5 respectively, them make the return statement as
return label.frame.height + 10
NOTE:- Width should be the width you want of the label. It should be according to your UITableView or UIView

Adjust UILabel height to text

I have some labels which I want to adjust their height to the text, this is the code I wrote for this now
func heightForView(text:String, font:UIFont, width:CGFloat) -> CGFloat{
let label:UILabel = UILabel(frame: CGRectMake(0, 0, width, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
EDIT:
The issue was not in this piece of code, so my fix is in the question itself. It might still be useful for others!
I've just put this in a playground and it works for me.
Updated for Swift 4.0
import UIKit
func heightForView(text:String, font:UIFont, width:CGFloat) -> CGFloat{
let label:UILabel = UILabel(frame: CGRectMake(0, 0, width, CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
let font = UIFont(name: "Helvetica", size: 20.0)
var height = heightForView("This is just a load of text", font: font, width: 100.0)
Swift 3:
func heightForView(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.height
}
If you are using AutoLayout, you can adjust UILabel height by config UI only.
For iOS8 or above
Set constraint leading/trailing for your UILabel
And change the lines of UILabel from 1 to 0
For iOS7
First, you need to add contains height for UILabel
Then, modify the Relation from Equal to Greater than or Equal
Finally, change the lines of UILabel from 1 to 0
Your UILabel will automatically increase height depending on the text
In swift 4.1 and Xcode 9.4.1
Only 3 steps
Step 1)
//To calculate height for label based on text size and width
func heightForView(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.height
}
Step 2)
//Call this function
let height = heightForView(text: "This is your text", font: UIFont.systemFont(ofSize: 17), width: 300)
print(height)//Output : 41.0
Step 3)
//This is your label
let proNameLbl = UILabel(frame: CGRect(x: 0, y: 20, width: 300, height: height))
proNameLbl.text = "This is your text"
proNameLbl.font = UIFont.systemFont(ofSize: 17)
proNameLbl.numberOfLines = 0
proNameLbl.lineBreakMode = .byWordWrapping
infoView.addSubview(proNameLbl)
I have the strong working solution.
in layoutSubviews:
title.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 0)
title.sizeToFit()
title.frame.size = title.bounds.size
in text setter:
title.text = newValue
setNeedsLayout()
UPD.
of course with this UILabel settings:
title.lineBreakMode = .byWordWrapping
title.numberOfLines = 0
I create this extension if you want
extension UILabel {
func setSizeFont (sizeFont: CGFloat) {
self.font = UIFont(name: self.font.fontName, size: sizeFont)!
self.sizeToFit()
}
}
based on Anorak's answer, I also agree with Zorayr's concern, so I added a couple of lines to remove the UILabel and return only the CGFloat, I don't know if it helps since the original code doesn't add the UIabel, but it doesn't throw error, so I'm using the code below:
func heightForView(text:String, font:UIFont, width:CGFloat) -> CGFloat{
var currHeight:CGFloat!
let label:UILabel = UILabel(frame: CGRectMake(0, 0, width, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.font = font
label.text = text
label.sizeToFit()
currHeight = label.frame.height
label.removeFromSuperview()
return currHeight
}
Just by setting:
label.numberOfLines = 0
The label automatically adjusts its height based upon the amount of text entered.
The solution suggested by Anorak as a computed property in an extension for UILabel:
extension UILabel
{
var optimalHeight : CGFloat
{
get
{
let label = UILabel(frame: CGRectMake(0, 0, self.frame.width, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = self.lineBreakMode
label.font = self.font
label.text = self.text
label.sizeToFit()
return label.frame.height
}
}
}
Usage:
self.brandModelLabel.frame.size.height = self.brandModelLabel.optimalHeight
Following on #Anorak answer, i added this extension to String and sent an inset as a parameter, because a lot of times you will need a padding to your text.
Anyway, maybe some you will find this usefull.
extension String {
func heightForWithFont(font: UIFont, width: CGFloat, insets: UIEdgeInsets) -> CGFloat {
let label:UILabel = UILabel(frame: CGRectMake(0, 0, width + insets.left + insets.right, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.font = font
label.text = self
label.sizeToFit()
return label.frame.height + insets.top + insets.bottom
}
}
Here is how to calculate the text height in Swift. You can then get the height from the rect and set the constraint height of the label or textView, etc.
let font = UIFont(name: "HelveticaNeue", size: 25)!
let text = "This is some really long text just to test how it works for calculating heights in swift of string sizes. What if I add a couple lines of text?"
let textString = text as NSString
let textAttributes = [NSFontAttributeName: font]
let textRect = textString.boundingRectWithSize(CGSizeMake(320, 2000), options: .UsesLineFragmentOrigin, attributes: textAttributes, context: nil)
just call this method where you need dynamic Height for label
func getHeightforController(view: AnyObject) -> CGFloat {
let tempView: UILabel = view as! UILabel
var context: NSStringDrawingContext = NSStringDrawingContext()
context.minimumScaleFactor = 0.8
var width: CGFloat = tempView.frame.size.width
width = ((UIScreen.mainScreen().bounds.width)/320)*width
let size: CGSize = tempView.text!.boundingRectWithSize(CGSizeMake(width, 2000), options:NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: tempView.font], context: context).size as CGSize
return size.height
}
Swift 4.0
self.messageLabel = UILabel(frame: CGRect(x: 70, y: 60, width:UIScreen.main.bounds.width - 80, height: 30)
messageLabel.text = message
messageLabel.lineBreakMode = .byWordWrapping //in versions below swift 3 (messageLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping)
messageLabel.numberOfLines = 0 //To write any number of lines within a label scope
messageLabel.textAlignment = .center
messageLabel.textColor = UIColor.white
messageLabel.font = messageLabel.font.withSize(12)
messageLabel.sizeToFit()
Blockquote NSParagraphStyle.LineBreakMode, apply to entire paragraphs, not words within paragraphs.This property is in effect both during normal drawing and in cases where the font size must be reduced to fit the label’s text in its bounding box. This property is set to byTruncatingTail by default.
This link describes the storyboard way of doing the same
Swift 4.0
Instead of calculating the text/label height, I just resize the label after inserting the (dynamic) text.
Assuming that myLabel is the UILabel in question:
let myLabel = UILabel(frame: CGRect(x: 0, y: 0, width: *somewidth*, height: *placeholder, e.g. 20*))
myLabel.numberOfLines = 0
myLabel.lineBreakMode = .byWordWrapping
...
And now comes the fun part:
var myLabelText: String = "" {
didSet {
myLabel.text = myLabelText
myLabel.sizeToFit()
}
}
The Swift 4.1 extension method to calculate label height:
extension UILabel {
func heightForLabel(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.height
}
}
Swift 5, XCode 11 storyboard way. I think this works for iOS 9 and higher. You want for example "Description" label to get the dynamic height, follow the steps:
1) Select description label -> Go to Attributes Inspector (pencil icon), set:
Lines: 0
Line Break: Word Wrap
2) Select your UILabel from storyboard and go to Size Inspector (ruler icon),
3) Go down to "Content Compression Resistance Priority to 1 for all other UIView (lables, buttons, imageview, etc) components that are interacting with your label.
For example, I have UIImageView, Title Label, and Description Label vertically in my view. I set Content Compression Resistance Priority to UIImageView and title label to 1 and for description label to 750. This will make a description label to take as much as needed height.
You can also use sizeThatFits function.
For example:
label.sizeThatFits(superView.frame.size).height
To make label dynamic in swift , don't give height constarint and in storyboard make label number of lines 0 also give bottom constraint and this is the best way i am handling dynamic label as per their content size .

Resources