@@ -79,6 +79,13 @@ public class TextLayoutManager: NSObject {
79
79
public var isInTransaction : Bool {
80
80
transactionCounter > 0
81
81
}
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
82
89
83
90
weak var layoutView : NSView ?
84
91
@@ -188,15 +195,26 @@ public class TextLayoutManager: NSObject {
188
195
189
196
// MARK: - Layout
190
197
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
+
191
206
/// Lays out all visible lines
192
207
func layoutLines( in rect: NSRect ? = nil ) { // swiftlint:disable:this function_body_length
208
+ assertNotInLayout ( )
193
209
guard layoutView? . superview != nil ,
194
210
let visibleRect = rect ?? delegate? . visibleRect,
195
211
!isInTransaction,
196
212
let textStorage else {
197
213
return
198
214
}
199
- CATransaction . begin ( )
215
+ #if DEBUG
216
+ isInLayout = true
217
+ #endif
200
218
let minY = max ( visibleRect. minY - verticalLayoutPadding, 0 )
201
219
let maxY = max ( visibleRect. maxY + verticalLayoutPadding, 0 )
202
220
let originalHeight = lineStorage. height
@@ -237,13 +255,11 @@ public class TextLayoutManager: NSObject {
237
255
}
238
256
} else {
239
257
// 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) )
241
259
}
242
260
newVisibleLines. insert ( linePosition. data. id)
243
261
}
244
262
245
- CATransaction . commit ( )
246
-
247
263
// Enqueue any lines not used in this layout pass.
248
264
viewReuseQueue. enqueueViews ( notInSet: usedFragmentIDs)
249
265
@@ -262,6 +278,9 @@ public class TextLayoutManager: NSObject {
262
278
delegate? . layoutManagerYAdjustment ( yContentAdjustment)
263
279
}
264
280
281
+ #if DEBUG
282
+ isInLayout = false
283
+ #endif
265
284
needsLayout = false
266
285
}
267
286
@@ -302,7 +321,7 @@ public class TextLayoutManager: NSObject {
302
321
let relativeMinY = max ( layoutData. minY - position. yPos, 0 )
303
322
let relativeMaxY = max ( layoutData. maxY - position. yPos, relativeMinY)
304
323
305
- for lineFragmentPosition in line. typesetter . lineFragments. linesStartingAt (
324
+ for lineFragmentPosition in line. lineFragments. linesStartingAt (
306
325
relativeMinY,
307
326
until: relativeMaxY
308
327
) {
0 commit comments