@@ -1091,15 +1091,35 @@ - (void)anyTextMayHaveBeenModified {
10911091 [self tryUpdatingHeight ];
10921092 // update active styles as well
10931093 [self tryUpdatingActiveStyles ];
1094+ // update drawing - schedule debounced relayout
1095+ [self scheduleRelayoutIfNeeded ];
1096+ }
1097+
1098+ // Debounced relayout helper - coalesces multiple requests into one per runloop tick
1099+ - (void )scheduleRelayoutIfNeeded
1100+ {
1101+ // Cancel any previously scheduled invocation to debounce
1102+ [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector (_performRelayout ) object: nil ];
1103+ // Schedule on next runloop cycle
1104+ [self performSelector: @selector (_performRelayout ) withObject: nil afterDelay: 0 ];
1105+ }
1106+
1107+ - (void )_performRelayout
1108+ {
1109+ if (!textView) { return ; }
1110+
1111+ dispatch_async (dispatch_get_main_queue (), ^{
1112+ NSRange wholeRange = NSMakeRange (0 , textView.textStorage .string .length );
1113+ NSRange actualRange = NSMakeRange (0 , 0 );
1114+ [textView.layoutManager invalidateLayoutForCharacterRange: wholeRange actualCharacterRange: &actualRange];
1115+ [textView.layoutManager ensureLayoutForCharacterRange: actualRange];
1116+ [textView.layoutManager invalidateDisplayForCharacterRange: wholeRange];
1117+ });
1118+ }
10941119
1095- // update drawing
1096- dispatch_async (dispatch_get_main_queue (), ^{
1097- NSRange wholeRange = NSMakeRange (0 , textView.textStorage .string .length );
1098- NSRange actualRange = NSMakeRange (0 , 0 );
1099- [textView.layoutManager invalidateLayoutForCharacterRange: wholeRange actualCharacterRange: &actualRange];
1100- [textView.layoutManager ensureLayoutForCharacterRange: actualRange];
1101- [textView.layoutManager invalidateDisplayForCharacterRange: wholeRange];
1102- });
1120+ - (void )didMoveToWindow {
1121+ [super didMoveToWindow ];
1122+ [self scheduleRelayoutIfNeeded ];
11031123}
11041124
11051125// MARK: - UITextView delegate methods
0 commit comments