Skip to content

Instantly share code, notes, and snippets.

@chrisjrex
Last active November 20, 2020 17:51
Show Gist options
  • Select an option

  • Save chrisjrex/c571056a4b621f7099bcbd5e179f184f to your computer and use it in GitHub Desktop.

Select an option

Save chrisjrex/c571056a4b621f7099bcbd5e179f184f to your computer and use it in GitHub Desktop.
ShrinkFontSizeToFit() when UILabel has numberOfLines = 0 and lineBreakMode = .byWordWrapping
import UIKit
extension UILabel {
func shrinkFontSizeToFit(minimumFontSize: CGFloat? = nil, additionalAttributes: [NSAttributedString.Key: Any] = [:]) {
guard self.bounds.size != .zero, let unwrappedText = self.text, let longestWord = String.findLongestWordByWidth(in: unwrappedText) else {
return
}
var attributes = self.currentAttributes().merging(additionalAttributes, uniquingKeysWith: { _, new in new })
let minFontSize = minimumFontSize ?? ceil(self.font.pointSize * self.minimumScaleFactor)
var fontSize = self.font.pointSize
// Find the smallest allowable font to fit the longest word into the width
while fontSize >= minFontSize {
let width = (longestWord as NSString).size(withAttributes: attributes).width
guard width > self.bounds.width else { break }
fontSize -= 1
attributes[.font] = self.font.withSize(fontSize)
}
// Find the smallest allowable font to fit the text into the height
while fontSize >= minFontSize {
let height = unwrappedText.height(fittingWidth: self.bounds.width, attributes: attributes)
guard height > self.bounds.size.height else {
// Resize the frame to fit the new font size
self.frame.size = CGSize(width: self.bounds.width, height: height)
break
}
fontSize -= 1
attributes[.font] = self.font.withSize(fontSize)
}
if let newFont = attributes[.font] as? UIFont, newFont.pointSize < self.font.pointSize {
self.font = newFont
self.setNeedsLayout()
}
}
func currentAttributes() -> [NSAttributedString.Key: Any] {
guard let text = self.text, text.count > 0 else {
return [:]
}
var range = NSRange(location: 0, length: text.count)
return self.attributedText?.attributes(at: 0, effectiveRange: &range) ?? [:]
}
}
extension String {
func height(fittingWidth width: CGFloat, attributes: [NSAttributedString.Key: Any]) -> CGFloat {
let rect = (self as NSString).boundingRect(with: CGSize(width: width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: attributes,
context: nil)
return ceil(rect.height)
}
func width(withAttributes attributes: [NSAttributedString.Key: Any]? = nil) -> CGFloat {
return (self as NSString).size(withAttributes: attributes).width
}
static func findLongestWordByWidth(in text: String, attributes: [NSAttributedString.Key: Any]? = nil) -> String? {
var cache: [String: CGFloat] = [:]
func calculateWidthAndCache(_ word: String) -> CGFloat {
let width = word.width(withAttributes: attributes)
cache[word] = width
return width
}
return text.components(separatedBy: .whitespacesAndNewlines).max { current, next in
let currentWidth = cache[current] ?? calculateWidthAndCache(current)
let nextWidth = cache[next] ?? calculateWidthAndCache(next)
return nextWidth > currentWidth
}
}
}
@chrisjrex
Copy link
Copy Markdown
Author

chrisjrex commented Nov 20, 2020

TODO: Create a high-level function for UILabel that will increase and decrease font size to fit. Rather than currently only decreasing font size

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment