Adventures with Text Kit, Part II
In Part I, I discussed a small but interesting problem around text layout that applications experience when placing a view inside a text view. Here in Part II, I’ll demonstrate a simple way to solve this with Text Kit.
Text Kit, a framework introduced in iOS 7, adds a substantial amount of control to text layout and rendering on iOS. It introduces new interrelated and interacting objects which store textual content, calculate text layout for that content within a container, and output that layout in a view. UITextView has been updated to communicate with this new layout and text container architecture and is now an output view for a Text Kit layout.
To review Day One’s problem: text in the text view collides with controls placed inside the text view:
We need the text view to reflow its content around the portion of its interior occupied by the control to fix this issue.
Each UITextView has an associated NSTextContainer, either created by the text view in its default initializer, or passed into its designated initializer (initWithFrame:textContainer:). An NSTextContainer is used by a layout manager to determine the appropriate regions for laying out fragments of text. NSTextContainer contains a property for excluding rectangular regions from this layout: exclusionPaths, which contains an array of UIBezierPaths. When the layout manager is calculating the layout for its text, it calculates a series of rectangles in which it would like to place line fragments. It queries the text container about each of these fragments. The text container determines whether a rectangle intersects a region defined by its exclusion paths, and shortens or fragments the rectangle accordingly.
With this piece of API, it’s very straightforward to have Text Kit reflow text around a particular region. Given a view whose frame overlaps a text view, create a UIBezierPath with that view’s frame, and add it to the UITextView’s NSTextContainer’s exclusionPaths.
UIBezierPath *exclusionPath = [UIBezierPath bezierPathWithRect:self.characterCountView.frame]; self.textView.textContainer.exclusionPaths = @[exclusionPath];
This solution works well for simple cases like the one shown above. Any view which is a subview of the text view only needs to have its frame excluded from the layout once. Interacting with the text view's scroll offset and modifying its content all work as expected, and no additional work is required to update the exclusion paths further.
Here's some examples of how Text Kit handles selection, scrolling, and text entry around the excluded region.
Text Kit - simple exclusion path from Christopher Bowns on Vimeo.
Text Kit - selection and reflow from Christopher Bowns on Vimeo.
Text Kit - text entry around exclusion path from Christopher Bowns on Vimeo.
For Day One, however, a different solution is required. The view we wish to exclude needs to be absolutely positioned with respect to the text view’s frame, and thus, as the text view scrolls, the calculated exclusion path changes according to the content offset. Additional work is required to correctly calculate these exclusion paths and update the text view at the correct times. In Part III, I’ll show how to solve for this case, which will provide us a solution appropriate for Day One.











