| Flutter iOS Embedder |

FlutterSpellCheckPlugin.mm
Go to the documentation of this file.
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 "flutter/shell/platform/darwin/ios/framework/Source/FlutterSpellCheckPlugin.h"
6
7 #import <Foundation/Foundation.h>
8 #import <UIKit/UIKit.h>
9
10 #import "flutter/fml/logging.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
12
13 // Method Channel name to start spell check.
14 static NSString* const kInitiateSpellCheck = @"SpellCheck.initiateSpellCheck";
15
16 @interface FlutterSpellCheckPlugin ()
17
18 @property(nonatomic, retain) UITextChecker* textChecker;
19
20 @end
21
22 @implementation FlutterSpellCheckPlugin
23
24 - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
25 if (!_textChecker) {
26 // UITextChecker is an expensive object to initiate, see:
27 // https://github.com/flutter/flutter/issues/104454. Lazily initialate the UITextChecker object
28 // until at first method channel call. We avoid using lazy getter for testing.
29 _textChecker = [[UITextChecker alloc] init];
30 }
31 NSString* method = call.method;
32 NSArray* args = call.arguments;
33 if ([method isEqualToString:kInitiateSpellCheck]) {
34 FML_DCHECK(args.count == 2);
35 id language = args[0];
36 id text = args[1];
37 if (language == [NSNull null] || text == [NSNull null]) {
38 // Bail if null arguments are passed from dart.
39 result(nil);
40 return;
41 }
42
43 NSArray<NSDictionary<NSString*, id>*>* spellCheckResult =
44 [self findAllSpellCheckSuggestionsForText:text inLanguage:language];
45 result(spellCheckResult);
46 }
47 }
48
49 // Get all the misspelled words and suggestions in the entire String.
50 //
51 // The result will be formatted as an NSArray.
52 // Each item of the array is a dictionary representing a misspelled word and suggestions.
53 // The format looks like:
54 // {
55 // startIndex: 0,
56 // endIndex: 5,
57 // suggestions: [hello, ...]
58 // }
59 //
60 // Returns nil if the language is invalid.
61 // Returns an empty array if no spell check suggestions.
62 - (NSArray<NSDictionary<NSString*, id>*>*)findAllSpellCheckSuggestionsForText:(NSString*)text
63 inLanguage:(NSString*)language {
64 // Transform Dart Locale format to iOS language format if necessary.
65 if ([language containsString:@"-"]) {
66 NSArray<NSString*>* languageCodes = [language componentsSeparatedByString:@"-"];
67 FML_DCHECK(languageCodes.count == 2);
68 NSString* lastCode = [[languageCodes lastObject] uppercaseString];
69 language = [NSString stringWithFormat:@"%@_%@", [languageCodes firstObject], lastCode];
70 }
71
72 if (![UITextChecker.availableLanguages containsObject:language]) {
73 return nil;
74 }
75
76 NSMutableArray<FlutterSpellCheckResult*>* allSpellSuggestions = [[NSMutableArray alloc] init];
77
78 FlutterSpellCheckResult* nextSpellSuggestion;
79 NSUInteger nextOffset = 0;
80 do {
81 nextSpellSuggestion = [self findSpellCheckSuggestionsForText:text
82 inLanguage:language
83 startingOffset:nextOffset];
84 if (nextSpellSuggestion != nil) {
85 [allSpellSuggestions addObject:nextSpellSuggestion];
86 nextOffset =
87 nextSpellSuggestion.misspelledRange.location + nextSpellSuggestion.misspelledRange.length;
88 }
89 } while (nextSpellSuggestion != nil && nextOffset < text.length);
90
91 NSMutableArray* methodChannelResult = [[[NSMutableArray alloc] init] autorelease];
92
93 for (FlutterSpellCheckResult* result in allSpellSuggestions) {
94 [methodChannelResult addObject:[result toDictionary]];
95 }
96
97 [allSpellSuggestions release];
98 return methodChannelResult;
99 }
100
101 // Get the misspelled word and suggestions.
102 //
103 // Returns nil if no spell check suggestions.
104 - (FlutterSpellCheckResult*)findSpellCheckSuggestionsForText:(NSString*)text
105 inLanguage:(NSString*)language
106 startingOffset:(NSInteger)startingOffset {
107 FML_DCHECK([UITextChecker.availableLanguages containsObject:language]);
108 NSRange misspelledRange =
109 [self.textChecker rangeOfMisspelledWordInString:text
110 range:NSMakeRange(0, text.length)
111 startingAt:startingOffset
112 wrap:NO
113 language:language];
114 if (misspelledRange.location == NSNotFound) {
115 // No misspelled word found
116 return nil;
117 }
118
119 // If no possible guesses, the API returns an empty array:
120 // https://developer.apple.com/documentation/uikit/uitextchecker/1621037-guessesforwordrange?language=objc
121 NSArray<NSString*>* suggestions = [self.textChecker guessesForWordRange:misspelledRange
122 inString:text
123 language:language];
124 FlutterSpellCheckResult* result =
125 [[[FlutterSpellCheckResult alloc] initWithMisspelledRange:misspelledRange
126 suggestions:suggestions] autorelease];
127 return result;
128 }
129
130 - (UITextChecker*)textChecker {
131 return _textChecker;
132 }
133
134 - (void)dealloc {
135 [_textChecker release];
136 [super dealloc];
137 }
138
139 @end
140
141 @implementation FlutterSpellCheckResult
142
143 - (instancetype)initWithMisspelledRange:(NSRange)range
144 suggestions:(NSArray<NSString*>*)suggestions {
145 self = [super init];
146 if (self) {
147 _suggestions = [suggestions copy];
148 _misspelledRange = range;
149 }
150 return self;
151 }
152
153 - (NSDictionary<NSString*, NSObject*>*)toDictionary {
154 NSMutableDictionary* result = [[[NSMutableDictionary alloc] initWithCapacity:3] autorelease];
155 result[@"startIndex"] = @(_misspelledRange.location);
156 // The end index represents the next index after the last character of a misspelled word to match
157 // the behavior of Dart's TextRange: https://api.flutter.dev/flutter/dart-ui/TextRange/end.html
158 result[@"endIndex"] = @(_misspelledRange.location + _misspelledRange.length);
159 result[@"suggestions"] = _suggestions;
160 return result;
161 }
162
163 - (void)dealloc {
164 [_suggestions release];
165 [super dealloc];
166 }
167
168 @end
FlutterSpellCheckPlugin
Definition: FlutterSpellCheckPlugin.h:13
FlutterSpellCheckResult
Definition: FlutterSpellCheckPlugin.h:19
kInitiateSpellCheck
static NSString *const kInitiateSpellCheck
Definition: FlutterSpellCheckPlugin.mm:14
FlutterSpellCheckResult::misspelledRange
NSRange misspelledRange
Definition: FlutterSpellCheckPlugin.h:22
FlutterMethodCall::method
NSString * method
Definition: FlutterCodecs.h:233
FlutterMethodCall
Definition: FlutterCodecs.h:220
FlutterSpellCheckPlugin.h
FlutterResult
void(^ FlutterResult)(id _Nullable result)
Definition: FlutterChannels.h:196
FlutterViewController_Internal.h
id
int32_t id
Definition: SemanticsObjectTestMocks.h:20
-[FlutterSpellCheckResult toDictionary]
NSDictionary< NSString *, NSObject * > * toDictionary()
Definition: FlutterSpellCheckPlugin.mm:153
FlutterMethodCall::arguments
id arguments
Definition: FlutterCodecs.h:238