stephan-tolksdorf / STULabel

A faster and more flexible label view for iOS
Other
121 stars 28 forks source link
cpp17 ios label objective-c swift text

CircleCI TravisCI Swift 4.2 Version Platform License Twitter

STULabel is an open source iOS framework for Swift and Objective-C that provides a label view (STULabel), a label layer (STULabelLayer) and a flexible API for thread-safe text layout and rendering (STUShapedString, STUTextFrame). The framework is implemented in Objective-C++ on top of the lower-level parts of the Core Text API. STULabel has a Swift overlay framework (STULabelSwift) that provides a convenient Swift API.

Table of Contents

STULabel features

The source code contains a demo app that you can build with the included Xcode project. The demo app contains:

Status

STULabel is pre-release ("beta") software. It has bugs, it needs more tests and it needs more documentation, yet it might already be good enough for your purposes. If you want to use it for anything serious, please subscribe to the bug tracker and update frequently.

The API and behaviour should be mostly stable now before the 1.0 release. (Binary interface (ABI) stability is an explicit non-goal of this open source library.)

License

Except where noted otherwise, everything in this repository is distributed under the terms of the 2-clause BSD license in LICENSE.txt.

The STULabel library incorporates data derived from the Unicode Character Database, which is distributed under the Unicode, Inc. License Agreement.

Integration

CocoaPods integration

If you want to use STULabel from Objective-C code, add the following to your Podfile:

pod 'STULabel', '~> 0.8.11'

If you want to use STULabel from Swift code, add the following to your Podfile:

pod 'STULabelSwift', '~> 0.8.11'

STULabel is a dependency of STULabelSwift.

Manual integration

LLDB formatters

The STULabel source contains an LLDB Python script that defines data formatters for various types defined by the library. If you find yourself stepping through STULabel code in Xcode, importing this script will improve your debugging experience.

Support

If you've found a bug, please create a GitHub issue. If possible, please provide sample code that reproduces the issue.

If you have a question regarding the use of STULabel, e.g. how to accomplish a certain text layout, please ask the question on Stack Overflow and tag it with 'STULabel'.

Some features in detail

Performance

Synchronous layout and rendering with STULabel is faster than UILabel and UITextView, sometimes several times faster. How much faster STULabel is depends both on the specific use case and on the device and iOS version. The Demo app contains a micro benchmark for label views that lets you compare the performance of STULabel, UILabel and UITextView for various test cases on your own devices.

STULabel is faster than UILabel mainly because it caches text layout data more aggressively. In part this is due to UILabel using NSStringDrawing for layout and rendering purposes, which doesn't support persisting the calculated text layout, while STULabel is using the STUTextFrame API (implemented on top of Core Text's CTTypesetter), which makes it very easy to separate the text shaping and layout from the text rendering.

UITextView seems to be primarily designed for lazily typesetting large mutable texts and supporting fine-grained customization of the layout process, not for displaying smallish static strings.

The automatic text scaling implementation in STULabel is particularly fast because instead of scaling down the font sizes it scales up the layout width and then scales down content during drawing. This has the advantage that the attributed string doesn't have to be recreated, font object caches are less loaded and most of the text shaping only has to be done once. The Core Graphics render quality on iOS should be equal for both approaches.

Async rendering

Text layout and rendering can constitute a large part of the total time that the main thread spends on layout and rendering, particularly if the text is written in languages using complex scripts, like e.g. Hindi or Arabic. The asynchronous layout and rendering support in STULabel makes it easy to move at least part of this work to a background thread. (Image decoding and drawing can be another main-thread performance bottleneck on iOS, but that is usually much simpler to move to a background thread than text layout and rendering).

The UITableView example in the Demo app lets you enable or disable async and prefetch rendering for the STULabel views and thus allows you to observe the effect that these features can have on scrolling performance. (If you're using a fast device, you may have to increase the auto scroll speed to make the difference obvious.)

You can enable asynchronous rendering for STULabel views simply by setting the displaysAsynchronously property to true.

Doing the full text layout asynchronously is a bit more complicated, since you'll have to do it e.g. in a collection view prefetch handler and you need to know all the relevant layout parameters in advance in order to configure the STULabelPrerenderer object.

However, often you don't need to do the full layout on a background thread to achieve absolutely smooth scrolling with 60 or 120 FPS. Doing just the text shaping in advance, by constructing STUShapedString instances for the attributed strings that you want to display, and then configuring the label views the with the shaped strings instead of the attributed strings will already improve layout performance considerably.

In certain situation asynchronous rendering can be detrimental to the user experience, e.g. when it leads to visible flickering or breaks animations. STULabel will automatically switch to synchronous rendering when it can easily detect such a situation, e.g. when the layout was initiated in a UIView animation block. When this automatic behaviour isn't sufficient, you can e.g. temporarily disable async rendering through the delegate interface.

Flexible text truncation

Limitations and differences compared to UILabel and UITextView

No Interface Builder support

Xcode's Interface Builder doesn't support UIFont, NSAttributedString, UIEdgeInsets or any enum type as the type of an IBInspectable property, so there's currently no way to make STULabel work in IB like UILabel or UITextView.

If you can live with the IBInspectable limitations, e.g. because your application only uses a fixed set of "styles", you can of course subclass STULabel and make the subclass IBDesignable .

Auto Layout support

The Auto Layout support for UILabel and UITextView in UIKit makes extensive use of private APIs. Consequently, the Auto Layout support in STULabel has certain limitations:

Line height

Display scale rounding

When Core Graphics draws non-emoji glyphs into a bitmap context, it will round up the vertical glyph position (assuming an upper-left origin) such that the baseline Y-coordinate falls on a pixel boundary, except if the text is rotated or the context has been configured to allow vertical subpixel positioning by explicitly setting both setShouldSubpixelPositionFonts(true) and setShouldSubpixelQuantizeFonts(false). (The precise font rendering behaviour of Core Graphics and Core Text is completely undocumented and there are no public API functions for reading the current configuration of a CGContext.)

UILabel, UITextView and STULabel all first calculate the text layout ignoring any display scale rounding.

When UILabel draws text, it adjusts the origin of the drawn text rectangle such that the Y-coordinate of the last baseline is rounded to the nearest pixel boundary. Thus, the exact position of the first baseline depends on the position of the last baseline.

UITextView and STULabel don't adjust the vertical text position like UILabel does.

UITextView leaves the baseline display scale rounding to Core Graphics.

STULabel anticipates the display scale rounding by Core Graphics. When it draws a line of text it adjusts the text matrix of the Core Graphics context such that the baseline falls on the next pixel boundary. This approach has the advantage that the rendered emoji and non-emoji glyphs always have the correct relative vertical alignment.

Probably due to these display scale rounding issues, the intrinsic content height or sizeThatFits of a UILabel or UITextView can in certain situations be 1 pixel too short. Similarly, the vertical position of an UILabel or UITextView baseline anchor can be off by 1 pixel. STULabel doesn't have these issues.

Alignment and layout bounds

Line breaking and truncation

Text attributes and decorations

Links

Other limitations

(This list is not complete.)