-
Notifications
You must be signed in to change notification settings - Fork 6k
iOS spell-checker ObjC #32941
iOS spell-checker ObjC #32941
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| #ifndef SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERSPELLCHECKPLUGIN_H_ | ||
| #define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERSPELLCHECKPLUGIN_H_ | ||
|
|
||
| #include "flutter/fml/memory/weak_ptr.h" | ||
|
|
||
| #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" | ||
| #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h" | ||
|
|
||
| @interface FlutterSpellCheckPlugin : NSObject | ||
| - (instancetype)init NS_UNAVAILABLE; | ||
| + (instancetype)new NS_UNAVAILABLE; | ||
| - (instancetype)initWithMethodChannel:(FlutterMethodChannel*)methodChannel | ||
| NS_DESIGNATED_INITIALIZER; | ||
|
|
||
| @end | ||
|
|
||
| @interface FlutterSpellCheckResult : NSObject | ||
|
|
||
| @property(nonatomic, copy, readonly) NSArray<NSString*>* suggestions; | ||
| @property(nonatomic, assign, readonly) NSRange misspelledRange; | ||
|
|
||
| - (instancetype)init NS_UNAVAILABLE; | ||
| + (instancetype)new NS_UNAVAILABLE; | ||
| - (instancetype)initWithMisspelledRange:(NSRange)range | ||
| suggestions:(NSArray<NSString*>*)suggestions NS_DESIGNATED_INITIALIZER; | ||
| - (NSDictionary<NSString*, id>*)toDictionary; | ||
|
|
||
| @end | ||
|
|
||
| #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERSPELLCHECKPLUGIN_H_ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSpellCheckPlugin.h" | ||
|
|
||
| #import <Foundation/Foundation.h> | ||
| #import <UIKit/UIKit.h> | ||
|
|
||
| #import "flutter/fml/logging.h" | ||
| #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" | ||
|
|
||
| // Method Channel name to start spell check. | ||
| static NSString* const kInitiateSpellCheck = @"SpellCheck.initiateSpellCheck"; | ||
|
|
||
| @interface FlutterSpellCheckPlugin () | ||
|
|
||
| @property(nonatomic, assign) FlutterMethodChannel* methodChannel; | ||
| @property(nonatomic, retain) UITextChecker* textChecker; | ||
|
|
||
| @end | ||
|
|
||
| @implementation FlutterSpellCheckPlugin | ||
|
|
||
| - (instancetype)initWithMethodChannel:(FlutterMethodChannel*)methodChannel { | ||
| self = [super init]; | ||
| if (self) { | ||
| [_methodChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { | ||
| if (!self) { | ||
| return; | ||
| } | ||
| [self handleMethodCall:call result:result]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this isn't a retain cycle in MRC since it doesn't increase the retain count. Does it need to check if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! We should check if self is nil. |
||
| }]; | ||
| _textChecker = [[UITextChecker alloc] init]; | ||
| } | ||
| return self; | ||
| } | ||
|
|
||
| - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { | ||
| NSString* method = call.method; | ||
| NSArray* args = call.arguments; | ||
| if ([method isEqualToString:kInitiateSpellCheck]) { | ||
| FML_DCHECK(args.count == 2); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assert both fields cannot be nil. |
||
| id language = args[0]; | ||
| id text = args[1]; | ||
| if (language == [NSNull null] || text == [NSNull null]) { | ||
| // Bail if null arguments are passed from dart. | ||
| result(nil); | ||
| return; | ||
| } | ||
|
|
||
| NSArray<NSDictionary<NSString*, id>*>* spellCheckResult = | ||
| [self findAllSpellCheckSuggestionsForText:text inLanguage:language]; | ||
| result(spellCheckResult); | ||
| } | ||
| } | ||
|
|
||
| // Get all the misspelled words and suggestions in the entire String. | ||
| // | ||
| // The result will be formatted as am NSArray. | ||
| // Each item of the array is a representation of a misspelled word and suggestions. | ||
| // The format of each item looks like this: | ||
| // { | ||
| // @"location": 0, | ||
| // @"length" : 5, | ||
| // @"suggestions": [@"suggestion1", @"suggestion2"..] | ||
| // } | ||
| // | ||
| // Returns nil if the language is invalid. | ||
| // Returns an empty array if no spell check suggestions. | ||
| - (NSArray<NSDictionary<NSString*, id>*>*)findAllSpellCheckSuggestionsForText:(NSString*)text | ||
| inLanguage:(NSString*)language { | ||
| if (![UITextChecker.availableLanguages containsObject:language]) { | ||
| return nil; | ||
| } | ||
|
|
||
| NSMutableArray<FlutterSpellCheckResult*>* allSpellSuggestions = [[NSMutableArray alloc] init]; | ||
|
|
||
| FlutterSpellCheckResult* nextSpellSuggestion; | ||
| NSUInteger nextOffset = 0; | ||
| do { | ||
| nextSpellSuggestion = [self findSpellCheckSuggestionsForText:text | ||
| inLanguage:language | ||
| startingOffset:nextOffset]; | ||
| if (nextSpellSuggestion != nil) { | ||
| [allSpellSuggestions addObject:nextSpellSuggestion]; | ||
| nextOffset = | ||
| nextSpellSuggestion.misspelledRange.location + nextSpellSuggestion.misspelledRange.length; | ||
| } | ||
| } while (nextSpellSuggestion != nil && nextOffset < text.length); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nextOffset < text.length :) |
||
|
|
||
| NSMutableArray* methodChannelResult = [[[NSMutableArray alloc] init] autorelease]; | ||
|
|
||
| for (FlutterSpellCheckResult* result in allSpellSuggestions) { | ||
| [methodChannelResult addObject:[result toDictionary]]; | ||
| } | ||
|
|
||
| [allSpellSuggestions release]; | ||
| return methodChannelResult; | ||
| } | ||
|
|
||
| // Get the misspelled word and suggestions. | ||
| // | ||
| // Returns nil if no spell check suggestions. | ||
| - (FlutterSpellCheckResult*)findSpellCheckSuggestionsForText:(NSString*)text | ||
| inLanguage:(NSString*)language | ||
| startingOffset:(NSInteger)startingOffset { | ||
| FML_DCHECK([UITextChecker.availableLanguages containsObject:language]); | ||
| NSRange misspelledRange = | ||
| [self.textChecker rangeOfMisspelledWordInString:text | ||
| range:NSMakeRange(0, text.length) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this API handle empty/nil strings?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It handles empty String, which I added a test for. |
||
| startingAt:startingOffset | ||
| wrap:NO | ||
| language:language]; | ||
| if (misspelledRange.location == NSNotFound) { | ||
| // No misspelled word found | ||
| return nil; | ||
| } | ||
|
|
||
| // If no possible guesses, the API returns an empty array: | ||
| // https://developer.apple.com/documentation/uikit/uitextchecker/1621037-guessesforwordrange?language=objc | ||
| NSArray<NSString*>* suggestions = [self.textChecker guessesForWordRange:misspelledRange | ||
| inString:text | ||
| language:language]; | ||
| FlutterSpellCheckResult* result = | ||
| [[[FlutterSpellCheckResult alloc] initWithMisspelledRange:misspelledRange | ||
| suggestions:suggestions] autorelease]; | ||
| return result; | ||
| } | ||
|
|
||
| - (UITextChecker*)textChecker { | ||
| return _textChecker; | ||
| } | ||
|
|
||
| - (void)dealloc { | ||
| [_textChecker release]; | ||
| [super dealloc]; | ||
| } | ||
|
|
||
| @end | ||
|
|
||
| @implementation FlutterSpellCheckResult | ||
|
|
||
| - (instancetype)initWithMisspelledRange:(NSRange)range | ||
| suggestions:(NSArray<NSString*>*)suggestions { | ||
| self = [super init]; | ||
| if (self) { | ||
| _suggestions = [suggestions copy]; | ||
| _misspelledRange = range; | ||
| } | ||
| return self; | ||
| } | ||
|
|
||
| - (NSDictionary<NSString*, id>*)toDictionary { | ||
| NSMutableDictionary* result = [[[NSMutableDictionary alloc] initWithCapacity:3] autorelease]; | ||
| result[@"location"] = @(_misspelledRange.location); | ||
| result[@"length"] = @(_misspelledRange.length); | ||
| result[@"suggestions"] = [[_suggestions copy] autorelease]; | ||
| return result; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ Failures for clang-tidy on /opt/s/w/ir/cache/builder/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSpellCheckPlugin.mm: |
||
| } | ||
|
|
||
| - (void)dealloc { | ||
| [_suggestions release]; | ||
| [super dealloc]; | ||
| } | ||
|
|
||
| @end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
drive by: new codecs should avoid using the JSON codec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What codec should be used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FlutterStandardMethodCodec