Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 3f27800

Browse files
[iOS TextInput] Avoid Unnecessary UndateEditingClient Calls (#21303)
1 parent 678653b commit 3f27800

File tree

2 files changed

+39
-19
lines changed

2 files changed

+39
-19
lines changed

shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -538,34 +538,30 @@ - (void)setTextInputClient:(int)client {
538538
}
539539

540540
// Return true if the new input state needs to be synced back to the framework.
541+
// TODO(LongCatIsLooong): setTextInputState should never call updateEditingState. Sending the
542+
// editing value back may overwrite the framework's updated editing value.
541543
- (BOOL)setTextInputState:(NSDictionary*)state {
542544
NSString* newText = state[@"text"];
543545
BOOL textChanged = ![self.text isEqualToString:newText];
544546
if (textChanged) {
545547
[self.inputDelegate textWillChange:self];
546548
[self.text setString:newText];
547549
}
548-
BOOL needsEditingStateUpdate = textChanged;
549550
NSInteger composingBase = [state[@"composingBase"] intValue];
550551
NSInteger composingExtent = [state[@"composingExtent"] intValue];
551552
NSRange composingRange = [self clampSelection:NSMakeRange(MIN(composingBase, composingExtent),
552553
ABS(composingBase - composingExtent))
553554
forText:self.text];
554-
FlutterTextRange* newMarkedRange =
555+
556+
self.markedTextRange =
555557
composingRange.length > 0 ? [FlutterTextRange rangeWithNSRange:composingRange] : nil;
556-
needsEditingStateUpdate =
557-
needsEditingStateUpdate ||
558-
(!newMarkedRange ? self.markedTextRange != nil
559-
: ![newMarkedRange isEqualTo:(FlutterTextRange*)self.markedTextRange]);
560-
self.markedTextRange = newMarkedRange;
561558

562559
NSRange selectedRange = [self clampSelectionFromBase:[state[@"selectionBase"] intValue]
563560
extent:[state[@"selectionExtent"] intValue]
564561
forText:self.text];
565562

566563
NSRange oldSelectedRange = [(FlutterTextRange*)self.selectedTextRange range];
567564
if (!NSEqualRanges(selectedRange, oldSelectedRange)) {
568-
needsEditingStateUpdate = YES;
569565
[self.inputDelegate selectionWillChange:self];
570566

571567
[self setSelectedTextRangeLocal:[FlutterTextRange rangeWithNSRange:selectedRange]];
@@ -580,8 +576,8 @@ - (BOOL)setTextInputState:(NSDictionary*)state {
580576
[self.inputDelegate textDidChange:self];
581577
}
582578

583-
// For consistency with Android behavior, send an update to the framework if anything changed.
584-
return needsEditingStateUpdate;
579+
// For consistency with Android behavior, send an update to the framework if the text changed.
580+
return textChanged;
585581
}
586582

587583
// Extracts the selection information from the editing state dictionary.
@@ -788,6 +784,8 @@ - (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelecte
788784
}
789785

790786
- (void)unmarkText {
787+
if (!self.markedTextRange)
788+
return;
791789
self.markedTextRange = nil;
792790
[self updateEditingState];
793791
}

shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ - (void)testTextChangesTriggerUpdateEditingClient {
226226
XCTAssertFalse([inputView setTextInputState:@{@"text" : @"AFTER"}]);
227227
}
228228

229-
- (void)testSelectionChangeTriggersUpdateEditingClient {
229+
- (void)testSelectionChangeDoesNotTriggerUpdateEditingClient {
230230
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init];
231231
inputView.textInputDelegate = engine;
232232

@@ -236,23 +236,23 @@ - (void)testSelectionChangeTriggersUpdateEditingClient {
236236

237237
BOOL shouldUpdate = [inputView
238238
setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @0, @"selectionExtent" : @3}];
239-
XCTAssertTrue(shouldUpdate);
239+
XCTAssertFalse(shouldUpdate);
240240

241241
shouldUpdate = [inputView
242242
setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @3}];
243-
XCTAssertTrue(shouldUpdate);
243+
XCTAssertFalse(shouldUpdate);
244244

245245
shouldUpdate = [inputView
246246
setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @2}];
247-
XCTAssertTrue(shouldUpdate);
247+
XCTAssertFalse(shouldUpdate);
248248

249249
// Don't send anything if there's nothing new.
250250
shouldUpdate = [inputView
251251
setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @2}];
252252
XCTAssertFalse(shouldUpdate);
253253
}
254254

255-
- (void)testComposingChangeTriggersUpdateEditingClient {
255+
- (void)testComposingChangeDoesNotTriggerUpdateEditingClient {
256256
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init];
257257
inputView.textInputDelegate = engine;
258258

@@ -263,22 +263,44 @@ - (void)testComposingChangeTriggersUpdateEditingClient {
263263

264264
BOOL shouldUpdate = [inputView
265265
setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @0, @"composingExtent" : @3}];
266-
XCTAssertTrue(shouldUpdate);
266+
XCTAssertFalse(shouldUpdate);
267267

268268
shouldUpdate = [inputView
269269
setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @3}];
270-
XCTAssertTrue(shouldUpdate);
270+
XCTAssertFalse(shouldUpdate);
271271

272272
shouldUpdate = [inputView
273273
setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @2}];
274-
XCTAssertTrue(shouldUpdate);
274+
XCTAssertFalse(shouldUpdate);
275275

276-
// Don't send anything if there's nothing new.
277276
shouldUpdate = [inputView
278277
setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @2}];
279278
XCTAssertFalse(shouldUpdate);
280279
}
281280

281+
- (void)testUITextInputAvoidUnnecessaryUndateEditingClientCalls {
282+
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init];
283+
inputView.textInputDelegate = engine;
284+
285+
__block int updateCount = 0;
286+
OCMStub([engine updateEditingClient:0 withState:[OCMArg isNotNil]])
287+
.andDo(^(NSInvocation* invocation) {
288+
updateCount++;
289+
});
290+
291+
[inputView unmarkText];
292+
// updateEditingClient shouldn't fire as the text is already unmarked.
293+
XCTAssertEqual(updateCount, 0);
294+
295+
[inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)];
296+
// updateEditingClient fires in response to setMarkedText.
297+
XCTAssertEqual(updateCount, 1);
298+
299+
[inputView unmarkText];
300+
// updateEditingClient fires in response to unmarkText.
301+
XCTAssertEqual(updateCount, 2);
302+
}
303+
282304
- (void)testUpdateEditingClientNegativeSelection {
283305
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init];
284306
inputView.textInputDelegate = engine;

0 commit comments

Comments
 (0)