iOS Performance Optimization: A New Method for Calculating Multi-line UILabel Height
This article introduces a novel approach to compute the height of multi‑line UILabels on iOS by pre‑caching character widths per font, demonstrates its implementation in Swift, compares performance against the system boundingRect method through extensive benchmarks, and shows significant speed improvements in real‑world app scenarios.
The author noticed that calling boundingRect(with:options:attributes:context:) for each label in a feed caused noticeable CPU time, leading to UI jitter when table view heights were calculated asynchronously.
Initial attempts involved wrapping the system call in an asynchronous block, but the height returned to the table view delegate could be stale, causing content offset shifts.
To solve this, a new strategy was devised: pre‑compute and cache the width of every character (Chinese, Latin letters, digits, common symbols) for a given font, store the results in a dictionary, and reuse them to calculate total string width and required label height without invoking boundingRect repeatedly.
Key Swift implementation ( StringCalculateManager ) includes:
@available(iOS 7.0, *)
open func boundingRect(with size: CGSize, options: NSStringDrawingOptions = [], attributes: [NSAttributedString.Key : Any]? = nil, context: NSStringDrawingContext?) -> CGRect
class StringCalculateManager {
static let shared = StringCalculateManager()
var fontDictionary = [String: [String: CGFloat]]()
// ... methods to createNewFont, calculateSize, save/read JSON cache, etc.
}The manager creates a width dictionary the first time a font is used, persists it to font_dictionary.json , and updates the cache lazily for unknown characters.
An extension on String provides a convenient API:
extension String {
func boundingRectFast(withMaxSize size: CGSize, font: UIFont) -> CGRect {
return StringCalculateManager.shared.calculateSize(withString: self, size: size, font: font)
}
}A demo project ( GitHub ) benchmarks the old system method against the new fast method. The test loops call title.boundingRect(...) (oldWay) and title.boundingRectFast(...) (newWay) for increasing iteration counts, measuring total duration.
func oldWay(value: Int) {
// uses system boundingRect
}
func newWay(value: Int) {
// uses boundingRectFast
}Results show that a single call to the system method can be slower due to one‑time initialization, but after warm‑up the fast method consistently outperforms it. In a million‑iteration test, the system method took ~129 000 ms while the fast method took ~29 800 ms (≈23 % of the original time).
Real‑world profiling on an iPhone 7 (iOS 12.1) revealed that the old asynchronous approach consumed 353 ms (2.1 % of main‑thread time) whereas the new cached approach consumed only 46 ms (0.2 %). Consequently, the project adopted the cached width calculation for label height.
Conclusion: caching per‑font character widths dramatically reduces the cost of multi‑line label height calculation, making it a practical optimization for iOS feed‑style interfaces.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.