@@ -1099,15 +1099,35 @@ - (void)anyTextMayHaveBeenModified {
10991099 [self tryUpdatingHeight ];
11001100 // update active styles as well
11011101 [self tryUpdatingActiveStyles ];
1102+ // update drawing - schedule debounced relayout
1103+ [self scheduleRelayoutIfNeeded ];
1104+ }
1105+
1106+ // Debounced relayout helper - coalesces multiple requests into one per runloop tick
1107+ - (void )scheduleRelayoutIfNeeded
1108+ {
1109+ // Cancel any previously scheduled invocation to debounce
1110+ [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector (_performRelayout ) object: nil ];
1111+ // Schedule on next runloop cycle
1112+ [self performSelector: @selector (_performRelayout ) withObject: nil afterDelay: 0 ];
1113+ }
1114+
1115+ - (void )_performRelayout
1116+ {
1117+ if (!textView) { return ; }
1118+
1119+ dispatch_async (dispatch_get_main_queue (), ^{
1120+ NSRange wholeRange = NSMakeRange (0 , textView.textStorage .string .length );
1121+ NSRange actualRange = NSMakeRange (0 , 0 );
1122+ [textView.layoutManager invalidateLayoutForCharacterRange: wholeRange actualCharacterRange: &actualRange];
1123+ [textView.layoutManager ensureLayoutForCharacterRange: actualRange];
1124+ [textView.layoutManager invalidateDisplayForCharacterRange: wholeRange];
1125+ });
1126+ }
11021127
1103- // update drawing
1104- dispatch_async (dispatch_get_main_queue (), ^{
1105- NSRange wholeRange = NSMakeRange (0 , textView.textStorage .string .length );
1106- NSRange actualRange = NSMakeRange (0 , 0 );
1107- [textView.layoutManager invalidateLayoutForCharacterRange: wholeRange actualCharacterRange: &actualRange];
1108- [textView.layoutManager ensureLayoutForCharacterRange: actualRange];
1109- [textView.layoutManager invalidateDisplayForCharacterRange: wholeRange];
1110- });
1128+ - (void )didMoveToWindow {
1129+ [super didMoveToWindow ];
1130+ [self scheduleRelayoutIfNeeded ];
11111131}
11121132
11131133// MARK: - UITextView delegate methods
0 commit comments