@@ -79,7 +79,8 @@ extension TextLayoutManager {
7979 let maxY = max ( visibleRect. maxY + verticalLayoutPadding, 0 )
8080 let originalHeight = lineStorage. height
8181 var usedFragmentIDs = Set < LineFragment . ID > ( )
82- var forceLayout : Bool = needsLayout
82+ let forceLayout : Bool = needsLayout
83+ var didLayoutChange = false
8384 var newVisibleLines : Set < TextLine . ID > = [ ]
8485 var yContentAdjustment : CGFloat = 0
8586 var maxFoundLineWidth = maxLineWidth
@@ -95,29 +96,17 @@ extension TextLayoutManager {
9596 let wasNotVisible = !visibleLineIds. contains ( linePosition. data. id)
9697 let lineNotEntirelyLaidOut = linePosition. height != linePosition. data. lineFragments. height
9798
98- if forceLayout || linePositionNeedsLayout || wasNotVisible || lineNotEntirelyLaidOut {
99- let lineSize = layoutLine (
99+ defer { newVisibleLines. insert ( linePosition. data. id) }
100+
101+ func fullLineLayout( ) {
102+ let ( yAdjustment, wasLineHeightChanged) = layoutLine (
100103 linePosition,
104+ usedFragmentIDs: & usedFragmentIDs,
101105 textStorage: textStorage,
102- layoutData : LineLayoutData ( minY: minY , maxY: maxY , maxWidth : maxLineLayoutWidth ) ,
103- laidOutFragmentIDs : & usedFragmentIDs
106+ yRange : minY..< maxY,
107+ maxFoundLineWidth : & maxFoundLineWidth
104108 )
105- let wasLineHeightChanged = lineSize. height != linePosition. height
106- if wasLineHeightChanged {
107- lineStorage. update (
108- atOffset: linePosition. range. location,
109- delta: 0 ,
110- deltaHeight: lineSize. height - linePosition. height
111- )
112-
113- if linePosition. yPos < minY {
114- // Adjust the scroll position by the difference between the new height and old.
115- yContentAdjustment += lineSize. height - linePosition. height
116- }
117- }
118- if maxFoundLineWidth < lineSize. width {
119- maxFoundLineWidth = lineSize. width
120- }
109+ yContentAdjustment += yAdjustment
121110#if DEBUG
122111 laidOutLines. insert ( linePosition. data. id)
123112#endif
@@ -128,12 +117,24 @@ extension TextLayoutManager {
128117 // - New lines being inserted & Lines being deleted (lineNotEntirelyLaidOut)
129118 // - Line updated for width change (wasLineHeightChanged)
130119
131- forceLayout = forceLayout || wasLineHeightChanged || lineNotEntirelyLaidOut
120+ didLayoutChange = didLayoutChange || wasLineHeightChanged || lineNotEntirelyLaidOut
121+ }
122+
123+ if forceLayout || linePositionNeedsLayout || wasNotVisible || lineNotEntirelyLaidOut {
124+ fullLineLayout ( )
132125 } else {
126+ if didLayoutChange || yContentAdjustment > 0 {
127+ // Layout happened and this line needs to be moved but not necessarily re-added
128+ let needsFullLayout = updateLineViewPositions ( linePosition)
129+ if needsFullLayout {
130+ fullLineLayout ( )
131+ continue
132+ }
133+ }
134+
133135 // Make sure the used fragment views aren't dequeued.
134136 usedFragmentIDs. formUnion ( linePosition. data. lineFragments. map ( \. data. id) )
135137 }
136- newVisibleLines. insert ( linePosition. data. id)
137138 }
138139
139140 // Enqueue any lines not used in this layout pass.
@@ -171,14 +172,50 @@ extension TextLayoutManager {
171172
172173 // MARK: - Layout Single Line
173174
175+ private func layoutLine(
176+ _ linePosition: TextLineStorage < TextLine > . TextLinePosition ,
177+ usedFragmentIDs: inout Set < LineFragment . ID > ,
178+ textStorage: NSTextStorage ,
179+ yRange: Range < CGFloat > ,
180+ maxFoundLineWidth: inout CGFloat
181+ ) -> ( CGFloat , wasLineHeightChanged: Bool ) {
182+ let lineSize = layoutLineViews (
183+ linePosition,
184+ textStorage: textStorage,
185+ layoutData: LineLayoutData ( minY: yRange. lowerBound, maxY: yRange. upperBound, maxWidth: maxLineLayoutWidth) ,
186+ laidOutFragmentIDs: & usedFragmentIDs
187+ )
188+ let wasLineHeightChanged = lineSize. height != linePosition. height
189+ var yContentAdjustment : CGFloat = 0.0
190+ var maxFoundLineWidth = maxFoundLineWidth
191+
192+ if wasLineHeightChanged {
193+ lineStorage. update (
194+ atOffset: linePosition. range. location,
195+ delta: 0 ,
196+ deltaHeight: lineSize. height - linePosition. height
197+ )
198+
199+ if linePosition. yPos < yRange. lowerBound {
200+ // Adjust the scroll position by the difference between the new height and old.
201+ yContentAdjustment += lineSize. height - linePosition. height
202+ }
203+ }
204+ if maxFoundLineWidth < lineSize. width {
205+ maxFoundLineWidth = lineSize. width
206+ }
207+
208+ return ( yContentAdjustment, wasLineHeightChanged)
209+ }
210+
174211 /// Lays out a single text line.
175212 /// - Parameters:
176213 /// - position: The line position from storage to use for layout.
177214 /// - textStorage: The text storage object to use for text info.
178215 /// - layoutData: The information required to perform layout for the given line.
179216 /// - laidOutFragmentIDs: Updated by this method as line fragments are laid out.
180217 /// - Returns: A `CGSize` representing the max width and total height of the laid out portion of the line.
181- private func layoutLine (
218+ private func layoutLineViews (
182219 _ position: TextLineStorage < TextLine > . TextLinePosition ,
183220 textStorage: NSTextStorage ,
184221 layoutData: LineLayoutData ,
@@ -256,4 +293,16 @@ extension TextLayoutManager {
256293 layoutView? . addSubview ( view, positioned: . below, relativeTo: nil )
257294 view. needsDisplay = true
258295 }
296+
297+ private func updateLineViewPositions( _ position: TextLineStorage < TextLine > . TextLinePosition ) -> Bool {
298+ let line = position. data
299+ for lineFragmentPosition in line. lineFragments {
300+ guard let view = viewReuseQueue. getView ( forKey: lineFragmentPosition. data. id) else {
301+ return true
302+ }
303+
304+ view. frame. origin = CGPoint ( x: edgeInsets. left, y: position. yPos + lineFragmentPosition. yPos)
305+ }
306+ return false
307+ }
259308}
0 commit comments