Make UILabel auto-adjust font size to screen width, but not to text length - ios

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)
}

Related

Find width of string (swift)

How do I find the width of a string (CGFloat) given the font name and font size?
(The goal is to set the width of a UIView to be just wide enough to hold the string.)
I have two strings: one with "1" repeated 36 times, the other with "M" repeated 36 times. These both fill the width (359.0) of the screen (give or take a little for margins).
I am using using Courier 16, which is monospaced, so I expect the width of both strings to be equal (as they in fact do appear on the screen).
However, using https://stackoverflow.com/a/58782429/8635708 :
the width of the string with the "1"s is 257.34375
the width of the string with the "M"s is 492.1875.
The first is does not fill the screen, the other is way too long.
And using https://stackoverflow.com/a/58795998/8635708 :
the width of each string is 249.640625.
At least here, they are the same, but that value clearly does not fill the screen.
I think you could create a label and call label.sizeToFit():
let label = UILabel()
label.font = UIFont.init(name: "Courier", size: 16)
label.text = "mmmmmmmmmmmmmmmm"//"1111111111111111"
label.sizeToFit()
print("Width: \(label.frame.size.width)") //153.66666666666666 -> For both strings

How to set UITextField width constraint to have room for a certain string

I have a UITextField that will display floating point values between 0 and 1.0 with 3 digits after the decimal point. So the widest text it will show is something like "0.000". I'd like to set the auto layout width constraint so that the text field always has just enough room to display this value.
The code below is close, but does not work.
let biggestString = "0.000"
let textAttrs = [NSAttributedStringKey.font: myField.font]
let size = (biggestString as NSString).size(withAttributes: textAttrs)
myField.widthAnchor.constraint(equalToConstant: size.width).isActive = true
It ends up displaying "1.0..." I'm guessing this is because a UITextField has some kind of padding around the text, so so I need to set the width to be the string width + the padding. But, I don't see a property from which I can read this padding amount. Is there a way to get it?
Try searching for the 'intrinsicContentSize'. According to the documentation this is what has to be set to indicate to the auto-layout how big the content is.
There was also a more elaborate discussion on how this can actually if the layout settings do not allow the resizing to work, see other question here:
How to increase width of textfield according to typed text?

Make responsive label font size for iphone devices

I am new to iOS Development.I am having problem with font size with phone screen size.For example Font size in iPhone 8 Plus looks fine but that text size is bigger in iPhone SE.I tried check Dynamic Type to Automatically Adjusts Font.And try to play with Autoshrink in StoryBoard.And i also tried to Add Font Variation in storyBoard.But I didnt get any good solution.Hope you understand my problem.Thanks in advance
Try this
class func dynamicFontSizeForIphone(fontSize : CGFloat) -> CGFloat
{
var current_Size : CGFloat = 0.0
current_Size = (UIScreen.main.bounds.width/320) //320*568 is my base
let FinalSize : CGFloat = fontSize * current_Size
return FinalSize
}
hope this work
You can change font size by using constraints.
1.take a label give its basic two constraint to satisfy. give one more constraint of equal.width to parent view. keep width as wide as your label text is.(a bit more than label text).
In attribute inspector there is a property name 'auto shrink' set it to 'minimum font size'
thats it.
Note: This will work fine if your Label text is constant. For changeable text there will be other approaches.

How to make multi-line UILabel text fit within predefined width without wrapping mid-word

I have a UILabel carefully laid out in Interface Builder with proper height and width constraints. The number of lines is set to 4. The wrapping is set to word wrap. The text is "CHECKED". The font size is very large and thus it only fits "CHECKE" and the "D" is on the second line. Writing "Checked" instead of "CHECKED" lets the font shrink (as intended) so that the whole word fits. But (the text is user given and it can be expected that the user writes fully uppercase words) having uppercase words the label does not break it/shrink the font as expected.
Do you have a suggestion as to what I might have missed? Capitalising the words (thusly only having the first letter uppercase) does work, but is not what the client wants.
Updated question
The problem seems to be unrelated to having uppercase or lowercase text. My problem could be solved by an answer to the following question:
How to make (ideally with the help of only Interface Builder) the UILabel text shrink trying to fit full words within all available lines without wrapping the text mid-word?
If the text "CHECKED" is too wide for a label (with more than 1 line available) it should shrink the font size instead of breaking the "D" and wrapping the single letter to the next line.
If the text is "one CHECKED two" and the single word "CHECKED" is already too wide for a label (with more than 1 line available) it should break between all words and shrinking the font size so that "CHECKED" still fits the middle line.
Avoiding:
one
CHECKE
D two
Thank you very much!
Here is a UILabel subclass that will find the largest word in the labels text, use the boundingRect function of NSString to see how large that one word will be with the current font, and drop the font size until it fits the width.
class AutosizingMultilineLabel: UILabel {
override func layoutSubviews() {
super.layoutSubviews()
self.adjustFontToFitWidth()
}
func adjustFontToFitWidth() {
guard let currentFont = self.font else { return }
let minimumFontSize: CGFloat = floor(self.minimumScaleFactor * currentFont.pointSize)
var newFontSize = currentFont.pointSize
var theNewFont = currentFont
if let text = self.text, let longestWord = text.components(separatedBy: " ").max(by: {$1.count > $0.count})?.replacingOccurrences(of: "\n", with: "") {
let nsString = longestWord as NSString
while newFontSize > minimumFontSize {
theNewFont = currentFont.withSize(newFontSize)
let boundingRect = nsString.boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude),
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [.font: theNewFont],
context: nil)
if ceil(boundingRect.size.width) <= self.bounds.size.width {
break
}
newFontSize -= 1
}
self.font = theNewFont
}
}
}
When the word is bigger than the line, word wrap doesn't work. If it doesn't fit on this line, it won't fit on the next line. (same word, same size, same line size). To make it fit, the label will start putting letters on the next line.
If you allow multiple lines on your label, the OS will try to fill the lines before adjusting the font size.
I think you're just running into a limitation on Autoshrink.
In Interface Builder:
add a new UILabel with Width: 230 and Height: 280
set the Font to System 44.0
set Line Break: Truncate Tail
set Autoshrink: Minimum Font Scale at 0.15
set the text of the label to test CHECKED lines
Now, drag the handle on the right edge of the label left and right... when it gets too narrow, the word CHECKED will break onto the next line.
Change CHECKED to checked and do the same thing. You should see the same behavior.
Now, try dragging the Bottom edge up and down. With either CHECKED or checked, you should see the Font Size auto shrink.
So... to do what you're trying to do, you might have to skip Autoshrink and instead do some code calculations.
Edit: further visual of what goes on...
Start with above values, but set the Height of the label to 170 - gives it just a little vertical padding.
Now, drag the left edge to make it narrower.
When you reach the end of the word CHECKED, and keep going, you will see the font shrink until it gets small enough that there is space for it to wrap to a 4th line.
I think you're going to need some code to get exactly what you need.

VirtualTreeView node height based on icon font size

I am trying to set node height based on system icon font size.
My code so far:
LOGFONT lf;
ZeroMemory(&lf, sizeof(lf));
if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0))
{
// This is a hack because font height itself doesn't give me correct node height - everything is too tight
int Height = VST->DefaultNodeHeight - abs(VST->Font->Height)+1;
VST->DefaultNodeHeight = abs(lf.lfHeight)+Height;
VST->Font->Name = lf.lfFaceName;
VST->Font->Height = lf.lfHeight;
}
The above works but I cannot get the DefaultNodeHeight right - it is not the same size as default used when font is fixed. Font height is good.
How can I retrieve the correct value from the system, or auto-size VirtualTreeView to use correct node height which would be the same as default but based on above code?
Default font height is -14, font size is 8 and node height is 18.
So in other words I need:
a) icon font size
b) appropriate DefaultNodeHeight, based on font size (in case if I use different font size, then DefaultNodeHeight is recalculated based on that particular size)

Resources