Skip to content

Commit ca32b4c

Browse files
committed
Document the layout routine
1 parent 120e6fc commit ca32b4c

File tree

1 file changed

+41
-0
lines changed

1 file changed

+41
-0
lines changed

Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager+Layout.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,47 @@ extension TextLayoutManager {
2626
// MARK: - Layout Lines
2727

2828
/// Lays out all visible lines
29+
///
30+
/// ## Overview Of The Layout Routine
31+
///
32+
/// The basic premise of this method is that it loops over all lines in the given rect (defaults to the visible
33+
/// rect), checks if the line needs a layout calculation, and performs layout on the line if it does.
34+
///
35+
/// The thing that makes this layout method so fast is the second point, checking if a line needs layout. To
36+
/// determine if a line needs a layout pass, the layout manager can check three things:
37+
/// - **1** Was the line laid out under the assumption of a different maximum layout width?
38+
/// Eg: If wrapping is toggled, and a line was initially long but now needs to be broken, this triggers that
39+
/// layout pass.
40+
/// - **2** Was the line previously not visible? This is determined by keeping a set of visible line IDs. If the
41+
/// line does not appear in that set, we can assume it was previously off screen and may need layout.
42+
/// - **3** Was the line entirely laid out? We break up lines into line fragments. When we do layout, we determine
43+
/// all line fragments but don't necessarily place them all in the view. This checks if all line fragments have
44+
/// been placed in the view. If not, we need to place them.
45+
///
46+
/// Once it has been determined that a line needs layout, we perform layout by recalculating it's line fragments,
47+
/// removing all old line fragment views, and creating new ones for the line.
48+
///
49+
/// ## Laziness
50+
///
51+
/// At the end of the layout pass, we clean up any old lines by updating the set of visible line IDs and fragment
52+
/// IDs. Any IDs that no longer appear in those sets are removed to save resources. This facilitates the text view's
53+
/// ability to only render text that is visible and saves tons of resources (similar to the lazy loading of
54+
/// collection or table views).
55+
///
56+
/// The other important lazy attribute is the line iteration. Line iteration is done lazily. As we iterate
57+
/// through lines and potentially update their heights, the next line is only queried for *after* the updates are
58+
/// finished.
59+
///
60+
/// ## Reentry
61+
///
62+
/// An important thing to note is that this method cannot be reentered. If a layout pass is begun while a layout
63+
/// pass is already ongoing, internal data structures will be broken. In debug builds, this is checked with a simple
64+
/// boolean and assertion.
65+
///
66+
/// To help ensure this property, all view modifications are done in a `CATransaction`. This ensures that only after
67+
/// we're done inserting and removing line fragment views, does macOS call `layout` on any related views. Otherwise,
68+
/// we may cause a layout pass when a line fragment view is inserted and cause a reentrance in this method.
69+
///
2970
/// - Warning: This is probably not what you're looking for. If you need to invalidate layout, or update lines, this
3071
/// is not the way to do so. This should only be called when macOS performs layout.
3172
public func layoutLines(in rect: NSRect? = nil) { // swiftlint:disable:this function_body_length

0 commit comments

Comments
 (0)