UILabel SizetoFit After Specifying Number of Lines? - swift

I would like to create a UILabel programmatically after specifying the number of lines. I am using Swift. I have the following code:
let newLabel = UILabel()
newLabel.text = "Hello There"
newLabel.font = UIFont.systemFont(ofSize: 16)
newLabel.numberOfLines = 2
newLabel.lineBreakMode = .byWordWrapping
newLabel.sizeToFit()
newLabel.frame.origin.x = 100
newLabel.frame.origin.y = 500
view.addSubview(newLabel)
The problem is that the label has the text on one line, rather than two lines. I have to use sizeToFit because the fontSize is actually dynamic (it is not always 16). How can I make sure that the label is 2 lines? Thanks.

You are not specifying exact frame of your UILabel.so your view just get any width it wants.you can use this for getting result:
newLabel.frame = CGRect(x: 100, y: 500, width: newLabel.frame.width - 1, height: newLabel.frame.height * 2)
But that's not very good and I suggest using AutoLayout

There are many things you can do to break line.
1) You can use "\n" in between the words. But not sure about your usecase and whether this make sense.
2) Thought not recommended you can define the width of the label.
3) Use numberOfLines = 0, if you can go to more than 2 lines. However, if you only want 2 lines then give numberOfLines = 2.
Also use Constraints something like below and not frame:
private let newLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello There"
label.font = UIFont.systemFont(ofSize: 16)
label.numberOfLines = 0
return label
}()
view.addSubview(newLabel)
NSLayoutConstraint.activate([
newLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 500),
newLabel.leadingAnchor.constraint(equalTo:view.leadingAnchor, constant: 100),
newLabel.widthAnchor.constraint(equalToConstant: 70)
])

Related

How do I Fade Label out at end instead of replacing end with "..." if it's too long / How to use GoogleToolboxForMac

I've found a solution with GTMFadeTruncatingLabelTest from GoogleToolboxForMac but don't really understand how to use it and I don't find anything about it
but if you have another solution besides of this one
If you can't help me with GoogleToolboxForMac feel free to suggest other solution
Should kinda look like this at the end:
I am not sure about the GTMFadeTruncatingLabelTest but I can offer an alternative solution.
Steps
Check if the label's text is going to be truncated
If 1 is true, Create a CAGradientLayer that goes from Opaque to Transparent
Apply the gradient layer as a mask to the UILabel
Implementation
If you don't want to read the rest, just grab the code from this repo
I wrapped step 1, 2 and 3 from above in a custom UILabel subclass. The reasoning is explained in the comments.
class FadingLabel: UILabel
{
// Add a property observer for text changes
// as we might not need to fade anymore
override var text: String?
{
didSet
{
// Check if the text needs to be faded
fadeTailIfRequired()
}
}
// Add a property observer for numberOfLines changes
// as only 1 line labels are supported for now
override var numberOfLines: Int
{
didSet
{
// Reset the number of lines to 1
if numberOfLines != 1
{
numberOfLines = 1
}
}
}
override func layoutSubviews()
{
super.layoutSubviews()
// The label's frame might have changed so check
// if the text needs to be faded or not
fadeTailIfRequired()
}
/// The function that handles fading the tail end of the text if the text goes
/// beyond the bounds of the label's width
private func fadeTailIfRequired()
{
// Reset the numberOfLines to 1
numberOfLines = 1
// Prevent processing fading when the library is in the middle of
// processing the string to truncate the ellipsis
if !isTruncatingEllipsis
{
// Check if the label's has it's width set and if the text goes
// beyond it's width plus a margin of safety
if bounds.width > CGFloat.zero && intrinsicContentSize.width > bounds.width + 5
{
// Fade label works better with this setting
allowsDefaultTighteningForTruncation = true
// Initialize and configure a gradient to start at the end of
// the label
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = [UIColor.white.cgColor, UIColor.clear.cgColor]
gradient.startPoint = CGPoint(x: 0.8, y: 0.5)
gradient.endPoint = CGPoint(x: 0.99, y: 0.5)
// Apply the gradient as a mask to the UILabel
layer.mask = gradient
// Remove ellipsis added as the default UILabel truncation character
removeEllipsis()
// We do not need to go beyond this point
return
}
// If the text has not been truncated, remove the gradient mask
if originalText == text
{
// Remove the layer mask
layer.mask = nil
}
}
}
/// Keep removing 1 character from the label till it no longer needs to truncate
private func removeEllipsis()
{
isTruncatingEllipsis = true
// Keep looping till we do not have the perfect string length
// to fit into the label
while intrinsicContentSize.width > bounds.width
{
// Drop the last character
text = String(text!.dropLast())
}
isTruncatingEllipsis = false
}
}
Then you can use it like a regular UILabel, for example:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let fadingLabelWithLongText = FadingLabel()
view.addSubview(fadingLabelWithLongText)
fadingLabelWithLongText.text = "Fading label with text more than it's bounds can handle"
fadingLabelWithLongText.textColor = .white
fadingLabelWithLongText.frame = CGRect(x: 20, y: 90, width: 250, height: 50)
let regularLabelWithLongText = UILabel()
view.addSubview(regularLabelWithLongText)
regularLabelWithLongText.text = "Regular label with text more than it's bounds can handle"
regularLabelWithLongText.textColor = .white
regularLabelWithLongText.frame = CGRect(x: 20, y: 160, width: 250, height: 50)
let fadingLabelWithShortText = UILabel()
view.addSubview(fadingLabelWithShortText)
fadingLabelWithShortText.text = "Fading label with text that fits"
fadingLabelWithShortText.textColor = .white
fadingLabelWithShortText.frame = CGRect(x: 20, y: 230, width: 250, height: 50)
let regularLabelWithShortText = UILabel()
view.addSubview(regularLabelWithShortText)
regularLabelWithShortText.text = "Regular label with text that fits"
regularLabelWithShortText.textColor = .white
regularLabelWithShortText.frame = CGRect(x: 20, y: 300, width: 250, height: 50)
}
Output
Limitation
This way only supports single line UILabels
Update
Added a function to remove the default truncation method of using ellipsis (three dots) by UILabel with this function.
/// Keep removing 1 character from the label till it no longer needs to truncate
private func removeEllipsis()
{
isTruncatingEllipsis = true
// Keep looping till we do not have the perfect string length
// to fit into the label
while intrinsicContentSize.width > bounds.width
{
// Drop the last character
text = String(text!.dropLast())
}
isTruncatingEllipsis = false
}
This function has been updated in the original code and repo mentioned above.
I Think the easiest way is to use the following code:
titleLabel.adjustsFontSizeToFitWidth = false
titleLabel.lineBreakMode = .byClipping
It automatically fades the last words if applicable and text size has more width than UILabel!
thanks to thi

How to add space between buttons in uiscrollview

I was trying so hard to add space between buttons array in UIScrollView but I couldn't make it. There is any way to make space between buttons array in UIScrollView ?
I have attached a picture explaining what I want.
This is my code :
//MARK: - SETUP CATEGORIES BUTTONS
func setupCategoriesButtons() {
var xCoord: CGFloat = 1
let yCoord: CGFloat = 1
let buttonHeight: CGFloat = 40
let buttonwidth: CGFloat = 40
let gap: CGFloat = 0
// Counter for items
var itemCount = 0
// Loop for creating buttons
for i in 0..<myArray.count {
itemCount = i
// Create a Button
let catButt = UIButton(type: .custom)
catButt.showsTouchWhenHighlighted = true
catButt.setTitle("\(myArray[itemCount])", for: .normal)
catButt.sizeToFit()
catButt.frame = CGRect(x: xCoord, y: yCoord, width: catButt.frame.size.width + 30, height: buttonHeight)
catButt.layer.cornerRadius = 8
catButt.titleLabel?.font = UIFont(name: "Titillium-Semibold", size: 12)
// catButt.addTarget(self, action: #selector(categoryButt(_:)), for: UIControlEvents.touchUpInside)
// Add Buttons & Labels based on xCood
xCoord += catButt.frame.size.width + gap
categoriesScrollView.addSubview(catButt)
letterButtons.append(catButt)
} // END LOOP --------------------------
for i in 0..<letterButtons.count {
letterButtons[i].tag = i
letterButtons[i].backgroundColor = RandomColor()
letterButtons[i].setTitleColor(UIColor.white, for: .normal)
}
// Place Buttons into the ScrollView
categoriesScrollView.contentSize = CGSize(width: buttonwidth * CGFloat(itemCount+1), height: yCoord)
}
let gap: CGFloat = 0
Looks wrong, if you want a gap. Shouldn't it be e.g. ... = 15?
Alternatives could include placing them in a UIStackView with a spacing set. This would also be simple using auto-layout, creating constraints between the buttons which provide the gaps.
Another suggestion:
I would suggest to use a UIStackView for achieving the desired appearance instead of adding the buttons directly in the scroll view. The gap would be represented as spacing property.
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.distribution = .equalSpacing
// here is the "gap":
stackView.spacing = 16.0
therefore, you should add the buttons in it:
stackView.addArrangedSubview(button)

how to programmatically make label larger?

Right now, the edges of the label line up exactly with the end of the text - I want to give the text some padding(?) or space so that you still see some of the background. Is this a constraints issue?
let lblTitle: UILabel = {
let lbl=UILabel()
lbl.text="Try this quiz."
lbl.textColor=UIColor.black
lbl.textAlignment = .justified
lbl.font = UIFont(name: "Gotham", size: 50)
lbl.backgroundColor = UIColor(white: 1, alpha: 0.8)
lbl.adjustsFontSizeToFitWidth = true
lbl.layer.masksToBounds = true
lbl.numberOfLines=0
lbl.sizeToFit()
lbl.layer.cornerRadius = 5
lbl.translatesAutoresizingMaskIntoConstraints=false
lbl.minimumScaleFactor = 0.1
return lbl
You probably should just use constraints (ie put the label in a container and give it space to to container equal to the padding you want, and only give the container 2 position constraints so it infers height and width from the label + padding), but if not its easy enough to do by adding a negative inset to the frame right after you call lbl.sizeToFit. For instance this adds 10 padding points in every direction:
lbl.frame.insetBy(dx: -20, dy: -20)

Xcode - Dynamic height only with top label

I am stuck with trying to only set dynamic height for the top label in the image provided. I have tried multiple things but I always end up with the row overlapping the content. Any help would be appreciated. I am using AutoLayout with Swift.
UPDATE:
Here are the current constraints that I have:
I have the following code in my viewDidLoad for the view controller:
notificationsTable.estimatedRowHeight = 85.0
notificationsTable.rowHeight = UITableViewAutomaticDimension
Do like this:
Image with long text:
Short text:
Image constraint:
Dynamic Label Constraint:
Normal text constraint:
Hope this help
Use this simple method:
func heightForView(_ text:String, 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.text = text
label.sizeToFit()
return label.frame.height
}
notificationsTable.rowHeight = heightForView(yourText, width: yourCGFloatWidth)

Self sizing label

I'm working on chat. So I stopped where I should do self-sizing labels to display text.
I tried to make my label as:
messageLabel.frame = CGRectMake(0, 0, self.chatScrollView.frame.size.width - 90, CGFloat.max)
messageLabel.sizeToFit()
messageLabel.frame.origin.x = (self.chatScrollView.frame.size.width - self.messageX) - messageLabel.frame.size.width
messageLabel.frame.origin.y = self.messageY
but my label didn't appear at all. If I make some exact height of my label for example 100 and comment line
//messageLabel.sizeToFit()
so the whole label code:
let messageLabel: UILabel = UILabel()
messageLabel.text = self.messageArray[i]
messageLabel.frame = CGRectMake(0, 0, self.chatScrollView.frame.size.width - 90, CGFloat.max)
messageLabel.backgroundColor = UIColor.groupTableViewBackgroundColor()
messageLabel.numberOfLines = 0
messageLabel.lineBreakMode = .ByWordWrapping
messageLabel.sizeToFit()
messageLabel.textAlignment = .Left
messageLabel.font = UIFont(name: "Apple SD Gothic Neo", size: 20)
messageLabel.textColor = UIColor.grayColor()
messageLabel.layer.zPosition = 20
messageLabel.frame.origin.x = (self.chatScrollView.frame.size.width - self.messageX) - messageLabel.frame.size.width
messageLabel.frame.origin.y = self.messageY
print(self.messageArray[i])
print("label origin \(messageLabel.frame.origin.x, messageLabel.frame.origin.y)")
self.chatScrollView.addSubview(messageLabel)
self.messageY += messageLabel.frame.size.height + 30
let width = self.view.frame.size.width
self.chatScrollView.contentSize = CGSizeMake(width, self.messageY)
self.chatScrollView.bringSubviewToFront(messageLabel)
Edit #1
Label definitely has text (print statement proofs it)
Label origin is on the screen area (second print statement proofs it)
Question: How I can fix it (or how I can make my label appear and be self-sizing label)?
I found the solution!
I just put my
messageLabel.text = self.messageArray[i]
right under the
let messageLabel: UILabel = UILabel()
so my label appeared!