Last active
November 20, 2020 17:51
-
-
Save chrisjrex/c571056a4b621f7099bcbd5e179f184f to your computer and use it in GitHub Desktop.
ShrinkFontSizeToFit() when UILabel has numberOfLines = 0 and lineBreakMode = .byWordWrapping
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |
| } | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TODO: Create a high-level function for UILabel that will increase and decrease font size to fit. Rather than currently only decreasing font size