I am trying to make my multiline UILabel as small as possible with given max-width, but the sizeToFit() and sizeThatFits(_,_) methods aren't giving the results I want.
Take a look at this image:
The red rectangle represents the width that I pass in sizeThatFits (while height being Max). Obviously, as you can see, this method does in fact return a size that fits, but it does not give me the smallest size possible, which I want.
Let's say I specified max-width: 300. This actual result is giving me a size of ca. 280*50.
As you can see in the image, the text is now written like this:
Here is some text that is supposed to
align nicely
What I want to achieve is this:
Here is some text that is
supposed to align nicely
This result would've had the same height, but a much smaller width, e.g 200*50
I realize that it's difficult to define "smallest size possible", as it could return this:
Here
is
some
text
that
...
Or even just a single letter per line. But given that sizeThatFits returns this, with a given width and height, why doesn't it return my wanted result, which is the same height, but with smaller width. Why isn't the smallest fit returned? Does a function like this exist?
Sti,
You can try this buddy :) I have been using it and seems to do a pretty good job :)
CGRect rect = [yourText boundingRectWithSize:CGSizeMake(maxWidth_you_Can_afford, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:16]}
context:nil];
The rect after executing will have the minimum width and height that will be required to render the text :)
Once you have the frame now you can set the same for your label and set the text as well :)
I have used it in chat bubble :) and it works well :)
Happy coding :)
I made my own solution, which works pretty well:
private func calculateWantedSize(label:UILabel)->CGSize{
var lastAcceptableWidth = label.bounds.width
let currentHeight = label.sizeThatFits(CGSize(width: label.bounds.width, height: CGFloat.max)).height
var tempHeight = currentHeight
while(tempHeight == currentHeight){
let newWidth = lastAcceptableWidth - 1
tempHeight = label.sizeThatFits(CGSize(width: newWidth, height: CGFloat.max)).height
if tempHeight == currentHeight{
lastAcceptableWidth = newWidth
}
}
return CGSize(width: lastAcceptableWidth, height: label.bounds.height)
}
Here I'm sending my label to a function which first calculates the current width and height of the label, then loops through width-1 and checks the resulting height until the height is changed. The height will change when the width is so low that it needs a new line. When this happens, I return the last valid width which still needed the same height as original.
This is perfect for my problem, using attributed text etc.
Of course, when using this, be sure to do some checking before calling. I don't know what will happen if you use this function when the label has a single word, or a single letter, or no text at all. I have only tested this for the cases I know will happen with this app, and it works well.
Related
In a very simple single screen app, I have a single-line UILabel going from left edge to right edge of the screen.
The text of the label is dynamically updated at runtime. The length of the text varies, as it contains a number in the range 0...100, and I am neither using a monospaced font nor leading zeroes.
Here is an illustration:
|<--------- Screen width --------------->|
|<----- UILabel "Some value = 0" ------->|
|<----- UILabel "Some value = 50" ------>|
|<----- UILabel "Some value = 100" ----->|
I would like the label to always use the maximum width for any device (i.e. screen size). This can be achieved by using auto-layout, suitable leading and trailing constraints, a large font size and the "auto-shrink" property for the label.
The problem is, that this approach will make the font size also vary depending on the value displayed, which is not what I want. It should only vary with the width of the screen, but not with the length of the label text.
In the example above, a large font size would be used for the value 0, a medium one for 50 and a small one for 100. I want it to adjust to the worst-case (100) and use the resulting size for any text afterwards.
Is it possible to achieve this using Interface-Builder properties and auto-layout constraints only?
I can think of ways how to calculate sizes in code, but I think there must be an easier way.
You cannot express font size as a proportion of view width in interface builder but you can do it very easily in code:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let textSize = Constant.baseFontSize * bounds.size.width / Constant.baseViewWidth
label.font = .systemFont(ofSize: textSize)
}
I have a UILabel that I need to have a specific text size and frame width (matching the screen width). I want to be able to set the label's height to the minimum height that will fit all of the label's text without cutting it off.
I've tried the following:
let label = UILabel()
label.text = longText
label.numberOfLines = 0
label.sizeToFit()
But this won't work if longText is something like "This string is really long, longer than the screen width" as sizeToFit puts that entire string on one line and the text gets cut off with ... when it reaches the screen width.
I then tried setting the label's width to match the screen width after calling sizeToFit. This lets the line wrap but doesn't adjust the label's height, so the string is still cut off after the first line.
Also, setting label.adjustsFontSizeToFitWidth = true won't work because I need the label to have a specific font size.
What I'd like to have is some kind of function like label.minimumHeightForWidth: where I can pass UIScreen.main.bounds.width as the width parameter and get a height that will fit the label's text on as many lines as needed with the given width parameter. Is it possible to do something like this?
You can try
let fixedWidth = UIScreen.main.bounds.width
let newSize = lbl.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
lbl.size = CGSize(width:fixedWidth, height: newSize.height)
My situation is that I have a line of text that can vary in length due to localization. This will need to be displayed on the screen such that each line is roughly of equal length, and is centered.
This is my very long line.
Should look like this
This is my
very long line.
So I took a crack at this and got something that works the way I want it now.
I take a localized string, set it to an empty label, and find out what it's size is. (The orange is just for illustrative purposes)
With the size of the label, I then divide it by 1.8 which gives me some buffer room to account for inconsistent word sizes (again, I don't know what will be here in advance). Finally, I multiply the height by 2.0, and set that as my new frame. Finally, I add it to the view.
This has held up with a few sample strings, though it would need to be revised to handle more than 2 lines (currently, not an issue).
let text = NSLocalizedString("This is my very long line of text.", comment: "")
let instructionLabel = UILabel()
instructionLabel.text = text
instructionLabel.textAlignment = .center
instructionLabel.backgroundColor = .orange
instructionLabel.numberOfLines = 0
let size = instructionLabel.intrinsicContentSize
let newSize = CGSize(width: size.width / 1.8, height: size.height * 2.0)
let rect = CGRect(x: 20, y: 100, width: newSize.width, height: newSize.height)
instructionLabel.frame = rect
view.addSubview(instructionLabel)
Which produces the following output:
And an even longer one:
Just for some variety, this is the second string above, but in Arabic:
You could do this to set alignment.
myLabel.textAlignment = .center
Also set the number of lines to 0. And if you want a specific width, set the preferredMaxLayoutWidth property like so:
myLabel.preferredMaxLayoutWidth = 80
myLabel.numberOfLines = 0
If you want it to work for arbitrary localizations (assuming languages that use spaces), you would need an algorithm that split the text on spaces and then loop through each combination of top and bottom text, measuring its width, to see what gave the most evenly distributed sizing. This feels like overkill.
Having done a fair amount of localization, the better bet is to manually insert \n characters in the .strings file to adjust breaks that aren't visually pleasing. Relying on a fixed width will work for many languages, but won't give you the flexibility you're looking for.
I'm trying to automatically layout text on a UILabel view.
The text (such as "abcdefghij") contains ten characters. I want to display it in one single line.
I turned off the Size Class and Auto Layout for convenience, and added following codes to layout the text on the UILabel. It should be ten characters in one line, and the width of the UILabel is equal to the width of the device.
let screenWidth = UIScreen.mainScreen().bounds.width
labelView.frame = CGRect(x: labelView.frame.origin.x, y: labelView.frame.origin.y, width: screenWidth, height: labelView.frame.height)
let string = "abcdefghij"
let stringLength = CGFloat(string.characters.count)
let characterSize = keyboardView.font.pointSize
let characterSpacing = (screenWidth - characterSize * stringLength) / stringLength
let content = NSAttributedString(string: string, attributes: [NSKernAttributeName: characterSpacing])
keyboardView.attributedText = content
But it turns out like this. The width of string is not equal to the screen
I think, the only could be wrong here is the pointSize. It equals to 13.8 while I set the font size to 17.
I don't understand it.
Give me some hints, please.
Thanks for your attention. 😄
By using sizeWithAttributes and boundingRectWithSize(_:options:context:), I finally figured out how it works. But my origin purpose is fitting the 10 characters in one line. The code should calculate the space between the characters, and all the space is same size. Could you give me some advices?
This is what I want to make
Each character occupies different amount of space depending on the character, font and size of the font.
Hence, you can use boundingRectWithSize(_:options:context:) to predict size of the string at runtime, and then take action according to your requirements.
I'm new to iPhone developing. I have a screen with image, title and content:
Image has dynamic size and also all text can have any length. What I want to achievie is to fit the image exactly in screen and make all labels to be only as long as they should be.
Afterk about 5 hours of work I have:
Working labels length, achieved by setting Lines=0 and Preferred Width=content width
Not working image, which lefts blank spaces
I have read a lot, I understand Content Hugging Priority and Content Compression Resistance Priority. I know differences in UIViewContentMode. I know that I can manually set UIImage.frame=CGRectMake(...). Nothing works. My image is more wider than higher (lets assume that width/height = 3). If I have UIViewContentMode=Aspect Fit then I will have blank space above and below the image. If set to Aspect fill then image is too high and also cropped. Changing frame in code doesn't change anything in view. What I want is to have image with size that was made using Aspect fit, but without blank space.
Finally I dit it. First of all, everyone writes that UIImageView.Frame should be modified to shrink the image, but be aware, that if your view is UITableViewCell then you should do that in layoutSubviews method (remember to invoke super.layoutSubviews() at the begging).
Secondly, make in IB a height constraint for your UIImageView for temporary value (for example 100). You will change that constraint in layoutSubviews method.
And finally: fitting image is NOT about its ratio. Set UIViewContentMode=Aspect Fit. Then the case with blank top and bottom areas occurs only when the initial image is wider than screen. Calculating the proper height should also take place in layoutSubviews.
Example implementation:
override func layoutSubviews() {
super.layoutSubviews()
var imageWidth = (imageViewObject.image?.size.width)!
var imageHeight = (imageViewObject.image?.size.height)!
var frameWidth = UIScreen.mainScreen().bounds.width
var properHeight: CGFloat
if imageWidth > frameWidth {
var ratio = frameWidth / imageWidth
properHeight = imageHeight * ratio
}
else {
properHeight = imageHeight
}
imageHeightConstraint.constant = properHeight
imageViewObject.frame = CGRectMake(0, 0, frameWidth, properHeight)
}