Skip to content

Commit e595437

Browse files
iOS UITextInput autocorrection prompt (flutter#13959)
1 parent 1a09328 commit e595437

File tree

7 files changed

+88
-27
lines changed

7 files changed

+88
-27
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginA
868868
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h
869869
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h
870870
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
871+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m
871872
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterUmbrellaImport.m
872873
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h
873874
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,13 @@ - (void)performAction:(FlutterTextInputAction)action withClient:(int)client {
598598
arguments:@[ @(client), actionString ]];
599599
}
600600

601+
- (void)showAutocorrectionPromptRectForStart:(NSUInteger)start
602+
end:(NSUInteger)end
603+
withClient:(int)client {
604+
[_textInputChannel.get() invokeMethod:@"TextInputClient.showAutocorrectionPromptRect"
605+
arguments:@[ @(client), @(start), @(end) ]];
606+
}
607+
601608
#pragma mark - Screenshot Delegate
602609

603610
- (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type

shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) {
3434
- (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state
3535
withClient:(int)client
3636
withPosition:(NSDictionary*)point;
37-
37+
- (void)showAutocorrectionPromptRectForStart:(NSUInteger)start
38+
end:(NSUInteger)end
39+
withClient:(int)client;
3840
@end
3941

4042
#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTDELEGATE_H_

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
@end
3737

3838
/** A range of text in the buffer of a Flutter text editing widget. */
39+
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
40+
FLUTTER_EXPORT
41+
#endif
3942
@interface FlutterTextRange : UITextRange <NSCopying>
4043

4144
@property(nonatomic, readonly) NSRange range;
@@ -44,4 +47,32 @@
4447

4548
@end
4649

50+
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
51+
FLUTTER_EXPORT
52+
#endif
53+
@interface FlutterTextInputView : UIView <UITextInput>
54+
55+
// UITextInput
56+
@property(nonatomic, readonly) NSMutableString* text;
57+
@property(nonatomic, readonly) NSMutableString* markedText;
58+
@property(readwrite, copy) UITextRange* selectedTextRange;
59+
@property(nonatomic, strong) UITextRange* markedTextRange;
60+
@property(nonatomic, copy) NSDictionary* markedTextStyle;
61+
@property(nonatomic, assign) id<UITextInputDelegate> inputDelegate;
62+
63+
// UITextInputTraits
64+
@property(nonatomic) UITextAutocapitalizationType autocapitalizationType;
65+
@property(nonatomic) UITextAutocorrectionType autocorrectionType;
66+
@property(nonatomic) UITextSpellCheckingType spellCheckingType;
67+
@property(nonatomic) BOOL enablesReturnKeyAutomatically;
68+
@property(nonatomic) UIKeyboardAppearance keyboardAppearance;
69+
@property(nonatomic) UIKeyboardType keyboardType;
70+
@property(nonatomic) UIReturnKeyType returnKeyType;
71+
@property(nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry;
72+
@property(nonatomic) UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0));
73+
@property(nonatomic) UITextSmartDashesType smartDashesType API_AVAILABLE(ios(11.0));
74+
75+
@property(nonatomic, assign) id<FlutterTextInputDelegate> textInputDelegate;
76+
77+
@end
4778
#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_

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

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -140,32 +140,6 @@ - (id)copyWithZone:(NSZone*)zone {
140140

141141
@end
142142

143-
@interface FlutterTextInputView : UIView <UITextInput>
144-
145-
// UITextInput
146-
@property(nonatomic, readonly) NSMutableString* text;
147-
@property(nonatomic, readonly) NSMutableString* markedText;
148-
@property(readwrite, copy) UITextRange* selectedTextRange;
149-
@property(nonatomic, strong) UITextRange* markedTextRange;
150-
@property(nonatomic, copy) NSDictionary* markedTextStyle;
151-
@property(nonatomic, assign) id<UITextInputDelegate> inputDelegate;
152-
153-
// UITextInputTraits
154-
@property(nonatomic) UITextAutocapitalizationType autocapitalizationType;
155-
@property(nonatomic) UITextAutocorrectionType autocorrectionType;
156-
@property(nonatomic) UITextSpellCheckingType spellCheckingType;
157-
@property(nonatomic) BOOL enablesReturnKeyAutomatically;
158-
@property(nonatomic) UIKeyboardAppearance keyboardAppearance;
159-
@property(nonatomic) UIKeyboardType keyboardType;
160-
@property(nonatomic) UIReturnKeyType returnKeyType;
161-
@property(nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry;
162-
@property(nonatomic) UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0));
163-
@property(nonatomic) UITextSmartDashesType smartDashesType API_AVAILABLE(ios(11.0));
164-
165-
@property(nonatomic, assign) id<FlutterTextInputDelegate> textInputDelegate;
166-
167-
@end
168-
169143
@implementation FlutterTextInputView {
170144
int _textInputClient;
171145
const char* _selectionAffinity;
@@ -554,6 +528,16 @@ - (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection
554528
// physical keyboard.
555529

556530
- (CGRect)firstRectForRange:(UITextRange*)range {
531+
// multi-stage text is handled somewhere else.
532+
if (_markedTextRange != nil) {
533+
return CGRectZero;
534+
}
535+
536+
NSUInteger start = ((FlutterTextPosition*)range.start).index;
537+
NSUInteger end = ((FlutterTextPosition*)range.end).index;
538+
[_textInputDelegate showAutocorrectionPromptRectForStart:start
539+
end:end
540+
withClient:_textInputClient];
557541
// TODO(cbracken) Implement.
558542
return CGRectZero;
559543
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import <OCMock/OCMock.h>
6+
#import <XCTest/XCTest.h>
7+
#include "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
8+
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
9+
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
10+
11+
FLUTTER_ASSERT_ARC
12+
13+
@interface FlutterTextInputPluginTest : XCTestCase
14+
@end
15+
16+
@implementation FlutterTextInputPluginTest
17+
18+
- (void)testAutocorrectionPromptRectAppears {
19+
// Setup test.
20+
id engine = OCMClassMock([FlutterEngine class]);
21+
22+
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithFrame:CGRectZero];
23+
inputView.textInputDelegate = engine;
24+
[inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]];
25+
26+
// Verify behavior.
27+
OCMVerify([engine showAutocorrectionPromptRectForStart:0 end:1 withClient:0]);
28+
29+
// Clean up mocks
30+
[engine stopMocking];
31+
}
32+
@end

testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
0D6AB6EB22BB40E700EEE540 /* FlutterEngineTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0D6AB6E722BB40CF00EEE540 /* FlutterEngineTest.mm */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
2121
0D6AB72C22BC339F00EEE540 /* libOCMock.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D6AB72522BC336100EEE540 /* libOCMock.a */; };
2222
0D6AB73F22BD8F0200EEE540 /* FlutterEngineConfig.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 0D6AB73E22BD8F0200EEE540 /* FlutterEngineConfig.xcconfig */; };
23+
3D8AF6182384C5420033B95F /* FlutterTextInputPluginTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D8AF6172384C5420033B95F /* FlutterTextInputPluginTest.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
2324
/* End PBXBuildFile section */
2425

2526
/* Begin PBXContainerItemProxy section */
@@ -94,6 +95,7 @@
9495
0D6AB6E722BB40CF00EEE540 /* FlutterEngineTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterEngineTest.mm; sourceTree = "<group>"; };
9596
0D6AB71722BC336100EEE540 /* OCMock.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OCMock.xcodeproj; path = ../../../../../third_party/ocmock/Source/OCMock.xcodeproj; sourceTree = "<group>"; };
9697
0D6AB73E22BD8F0200EEE540 /* FlutterEngineConfig.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = FlutterEngineConfig.xcconfig; sourceTree = "<group>"; };
98+
3D8AF6172384C5420033B95F /* FlutterTextInputPluginTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FlutterTextInputPluginTest.m; sourceTree = "<group>"; };
9799
/* End PBXFileReference section */
98100

99101
/* Begin PBXFrameworksBuildPhase section */
@@ -176,6 +178,7 @@
176178
children = (
177179
0D52D3B622C566D50011DEBD /* FlutterBinaryMessengerRelayTest.mm */,
178180
0D6AB6E722BB40CF00EEE540 /* FlutterEngineTest.mm */,
181+
3D8AF6172384C5420033B95F /* FlutterTextInputPluginTest.m */,
179182
0D17A5BF22D78FCD0057279F /* FlutterViewControllerTest.m */,
180183
0D4C3FAF22DF9F5300A67C70 /* FlutterPluginAppLifeCycleDelegateTest.m */,
181184
);
@@ -387,6 +390,7 @@
387390
buildActionMask = 2147483647;
388391
files = (
389392
0D6AB6EB22BB40E700EEE540 /* FlutterEngineTest.mm in Sources */,
393+
3D8AF6182384C5420033B95F /* FlutterTextInputPluginTest.m in Sources */,
390394
0D17A5C022D78FCD0057279F /* FlutterViewControllerTest.m in Sources */,
391395
0D1CE5D8233430F400E5D880 /* FlutterChannelsTest.m in Sources */,
392396
0D52D3BD22C566D50011DEBD /* FlutterBinaryMessengerRelayTest.mm in Sources */,

0 commit comments

Comments
 (0)