@@ -79,6 +79,13 @@ public class TextLayoutManager: NSObject {
7979 public var isInTransaction : Bool {
8080 transactionCounter > 0
8181 }
82+ #if DEBUG
83+ /// Guard variable for an assertion check in debug builds.
84+ /// Ensures that layout calls are not overlapping, potentially causing layout issues.
85+ /// This is used over a lock, as locks in performant code such as this would be detrimental to performance.
86+ /// Also only included in debug builds. DO NOT USE for checking if layout is active or not. That is an anti-pattern.
87+ private var isInLayout : Bool = false
88+ #endif
8289
8390 weak var layoutView : NSView ?
8491
@@ -188,15 +195,26 @@ public class TextLayoutManager: NSObject {
188195
189196 // MARK: - Layout
190197
198+ /// Asserts that the caller is not in an active layout pass.
199+ /// See docs on ``isInLayout`` for more details.
200+ private func assertNotInLayout( ) {
201+ #if DEBUG // This is redundant, but it keeps the flag debug-only too which helps prevent misuse.
202+ assert ( !isInLayout, " layoutLines called while already in a layout pass. This is a programmer error. " )
203+ #endif
204+ }
205+
191206 /// Lays out all visible lines
192207 func layoutLines( in rect: NSRect ? = nil ) { // swiftlint:disable:this function_body_length
208+ assertNotInLayout ( )
193209 guard layoutView? . superview != nil ,
194210 let visibleRect = rect ?? delegate? . visibleRect,
195211 !isInTransaction,
196212 let textStorage else {
197213 return
198214 }
199- CATransaction . begin ( )
215+ #if DEBUG
216+ isInLayout = true
217+ #endif
200218 let minY = max ( visibleRect. minY - verticalLayoutPadding, 0 )
201219 let maxY = max ( visibleRect. maxY + verticalLayoutPadding, 0 )
202220 let originalHeight = lineStorage. height
@@ -237,13 +255,11 @@ public class TextLayoutManager: NSObject {
237255 }
238256 } else {
239257 // Make sure the used fragment views aren't dequeued.
240- usedFragmentIDs. formUnion ( linePosition. data. typesetter . lineFragments. map ( \. data. id) )
258+ usedFragmentIDs. formUnion ( linePosition. data. lineFragments. map ( \. data. id) )
241259 }
242260 newVisibleLines. insert ( linePosition. data. id)
243261 }
244262
245- CATransaction . commit ( )
246-
247263 // Enqueue any lines not used in this layout pass.
248264 viewReuseQueue. enqueueViews ( notInSet: usedFragmentIDs)
249265
@@ -262,6 +278,9 @@ public class TextLayoutManager: NSObject {
262278 delegate? . layoutManagerYAdjustment ( yContentAdjustment)
263279 }
264280
281+ #if DEBUG
282+ isInLayout = false
283+ #endif
265284 needsLayout = false
266285 }
267286
@@ -302,7 +321,7 @@ public class TextLayoutManager: NSObject {
302321 let relativeMinY = max ( layoutData. minY - position. yPos, 0 )
303322 let relativeMaxY = max ( layoutData. maxY - position. yPos, relativeMinY)
304323
305- for lineFragmentPosition in line. typesetter . lineFragments. linesStartingAt (
324+ for lineFragmentPosition in line. lineFragments. linesStartingAt (
306325 relativeMinY,
307326 until: relativeMaxY
308327 ) {
0 commit comments