UILabel: adjustsFontSizeToFitWidth doesn't work
UIKit needs a hint about how many lines of text you want, or it doesn't know how much to scale down. If you want the whole text to fit into the label on one line, you also need: nameLabel.numberOfLines = 1
.
Working playground example:
import Foundation
import UIKit
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
label.numberOfLines = 1
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.5
label.text = "Some very long text goes here"
adjustsFontSizeToFitWidth has different effect on UILabel than on UITextField
I think it's fair to say that .adjustsFontSizeToFitWidth = true
only "mostly" works.
It is also primarily for use with string width.
Also, UILabel
and UITextField
do not use the same font rendering, and do not have the same bounding-box (text fields have an inset).
If you want both elements to have the same visual behaviors, your best bet is to use a UITextField
with user interaction disabled instead of a UILabel
.
Multiline UILabel with adjustsFontSizeToFitWidth
In this question, 0x90 provides a solution that - although a bit ugly - does what I want. Specifically, it deals correctly with the situation that a single word does not fit the width at the initial font size. I've slightly modified the code so that it works as a category on NSString
:
- (CGFloat)fontSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
CGFloat fontSize = [font pointSize];
CGFloat height = [self sizeWithFont:font constrainedToSize:CGSizeMake(size.width,FLT_MAX) lineBreakMode:UILineBreakModeWordWrap].height;
UIFont *newFont = font;
//Reduce font size while too large, break if no height (empty string)
while (height > size.height && height != 0) {
fontSize--;
newFont = [UIFont fontWithName:font.fontName size:fontSize];
height = [self sizeWithFont:newFont constrainedToSize:CGSizeMake(size.width,FLT_MAX) lineBreakMode:UILineBreakModeWordWrap].height;
};
// Loop through words in string and resize to fit
for (NSString *word in [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
CGFloat width = [word sizeWithFont:newFont].width;
while (width > size.width && width != 0) {
fontSize--;
newFont = [UIFont fontWithName:font.fontName size:fontSize];
width = [word sizeWithFont:newFont].width;
}
}
return fontSize;
}
To use it with a UILabel
:
CGFloat fontSize = [label.text fontSizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:label.frame.size];
label.font = [UIFont boldSystemFontOfSize:fontSize];
EDIT: Fixed the code to initialize newFont
with font
. Fixes a crash under certain circumstances.
Swift - Adjusting fontSize to fit the width of the layout (programmatically)
Try the following commands for your label:
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.2
And try to change the lines of the label to 0 and 1 (check both cases):
label.numberOfLines = 0 // or 1
adjustsFontSizeToFitWidth in utilities pane in Xcode storyboard
Select your label in the storyboard, and set the drop down to Minimum Font Size. Then in the text field, enter the lowest size your label should drop to to try to fit the contents onscreen:
How to manage text size in segment control for different devices?
Below is one way I can think of doing it but by no means the only way.
First, I started by setting up a collection view with a horizontal flow layout.
// Flow layout configuration, mainly to figure out width of the cells
private func createLayout() -> UICollectionViewFlowLayout {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.minimumLineSpacing = horizontalPadding
flowLayout.minimumInteritemSpacing = 0
flowLayout.scrollDirection = .horizontal
// Calculate the available width to divide the segments evenly
var availableWidth = UIScreen.main.bounds.width
// There will always be segments - 1 gaps, for 3 segments, there will be
// 2 gaps and for 4 segments there will be 3 gaps etc
availableWidth -= horizontalPadding * CGFloat(segments.count - 1)
let cellWidth = availableWidth / CGFloat(segments.count)
flowLayout.itemSize = CGSize(width: cellWidth,
height: collectionViewHeight)
return flowLayout
}
Once I do this, I run into the same problem, that depending on the width of the screen, my text could get cut off.
So once the width of each segment is determined, we have to calculate the maximum font size to show the complete text for the longest segment and that font size should be applied to all
In this case, the long segment is Vibration Intensity
in terms of string length.
// Flow layout configuration, mainly to figure out width of the cells
private func createLayout() -> UICollectionViewFlowLayout {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.minimumLineSpacing = horizontalPadding
flowLayout.minimumInteritemSpacing = 0
flowLayout.scrollDirection = .horizontal
flowLayout.sectionInset = UIEdgeInsets(top: 0,
left: horizontalPadding,
bottom: 0,
right: horizontalPadding)
// Calculate the available width to divide the segments evenly
var availableWidth = UIScreen.main.bounds.width
// There will always be segments - 1 gaps, for 3 segments, there will be
// 2 gaps and for 4 segments there will be 3 gaps etc
availableWidth -= horizontalPadding * CGFloat(segments.count - 1)
// Remove the insets
availableWidth -= flowLayout.sectionInset.left + flowLayout.sectionInset.right
let cellWidth = availableWidth / CGFloat(segments.count)
// Add this function
calculateApproxFontSize(forWidth: cellWidth)
flowLayout.itemSize = CGSize(width: cellWidth,
height: collectionViewHeight)
return flowLayout
}
private func calculateApproxFontSize(forWidth width: CGFloat) {
// Get the longest segment by length
if let longestSegmentTitle = segments.max(by: { $1.count > $0.count }) {
let tempLabel = UILabel()
tempLabel.numberOfLines = 1
tempLabel.text = longestSegmentTitle
tempLabel.sizeToFit()
guard var currentFont = tempLabel.font else { return }
var intrinsicSize
= (longestSegmentTitle as NSString).size(withAttributes: [.font : currentFont])
// Keep looping and reduce the font size till the text
// fits into the label
// This could be optimized further using binary search
// However this should be ok for small strings
while intrinsicSize.width > width
{
currentFont = currentFont.withSize(currentFont.pointSize - 1)
tempLabel.font = currentFont
intrinsicSize
= (longestSegmentTitle as NSString).size(withAttributes: [.font : currentFont])
}
// Set the font of the current label
// segmentFontSize is a global var in the VC
segmentFontSize = currentFont.pointSize
}
}
Then set the segmentFontSize
in cellForItemAt indexPath
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView
.dequeueReusableCell(withReuseIdentifier: SegmentCell.reuseIdentifier,
for: indexPath) as! SegmentCell
cell.backgroundColor = .orange
cell.title.text = segments[indexPath.item]
// Adjust the font
cell.title.font = cell.title.font.withSize(segmentFontSize)
return cell
}
This will give you something like this:
If you found some part difficult to follow, here is the link to the complete code: https://gist.github.com/shawn-frank/03bc06d13f90a54e23e9ea8c6f30a70e
Related Topics
How to Update UIviewrepresentable with Observableobject
How to Catch a Nsinternalinconsistencyexception in Swift
"This Class Is Not Key Value Coding-Compliant" Using Coreimage
What Was The Reason for Swift Assignment Evaluation to Void
How to Convert Unicode Character to Int in Swift
How to Get Textfields from Static Cells in UItableviewcontroller? Swift
Emitting a Warning for a Deprecated Swift Protocol Method in Implementing Types
Should I Keep Extensions in Their Own "Extensions" File
Swift Codable: Decode Dictionary with Unknown Keys
Levenshtein Distance in Swift3
Why Do I Have to Explicitly Unwrap My String in This Case
Access Parent Project Other_Swift_Flags from Pod
Alamofire Post Request with JSON Encoding
Swift - Boundingbox Cause Exc_Bad_Access (Code=1)