-
Notifications
You must be signed in to change notification settings - Fork 6k
[ios_edit_menu]add native edit menu #50095
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 |
|---|---|---|
|
|
@@ -149,11 +149,36 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { | |
| } else if ([method isEqualToString:@"Share.invoke"]) { | ||
| [self showShareViewController:args]; | ||
| result(nil); | ||
| } else if ([method isEqualToString:@"ContextMenu.showSystemContextMenu"]) { | ||
| [self showSystemContextMenu:args]; | ||
| result(nil); | ||
| } else if ([method isEqualToString:@"ContextMenu.hideSystemContextMenu"]) { | ||
| [self hideSystemContextMenu]; | ||
| result(nil); | ||
| } else { | ||
| result(FlutterMethodNotImplemented); | ||
| } | ||
| } | ||
|
|
||
| - (void)showSystemContextMenu:(NSDictionary*)args { | ||
|
Contributor
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. One possible problem I just thought of is the lack of a hideSystemContextMenu. Normally this is fine because the system context menu will hide itself when the text input connection is closed or when the user taps outside of the menu. However, it could be a problem if the user rebuilds their text field with a new context menu. Is it possible to add a hideSystemContextMenu method, and do you think it's useful?
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 should be easy to add. Let me try!
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. Just added
If it's easy for you, I'm open to just fix it here. But if it's hard, we can also leave it, and I can create an issue to track that (no strong opinion)
Contributor
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. Ok I'll play around with the hot restart thing and see if I can fix it, otherwise we'll open an issue.
Contributor
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. Thanks for the quick turnaround on hideSystemContextMenu. I'll try it out and post back. |
||
| if (@available(iOS 16.0, *)) { | ||
| FlutterTextInputPlugin* textInputPlugin = [_engine.get() textInputPlugin]; | ||
| BOOL shownEditMenu = [textInputPlugin showEditMenu:args]; | ||
| if (!shownEditMenu) { | ||
| FML_LOG(ERROR) << "Only text input supports system context menu for now. Ensure the system " | ||
| "context menu is shown with an active text input connection. See " | ||
| "https://github.com/flutter/flutter/issues/143033."; | ||
|
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. @justinmc added this log if fails to show edit menu (e.g. when calling from non-text input).
Contributor
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. Could you add this sentence if it's not too long? "Ensure the system context menu is shown with an active text input connection." That way it's more clear what someone needs to do to fix this, if it wasn't already obvious. |
||
| } | ||
| } | ||
| } | ||
|
|
||
| - (void)hideSystemContextMenu { | ||
| if (@available(iOS 16.0, *)) { | ||
| FlutterTextInputPlugin* textInputPlugin = [_engine.get() textInputPlugin]; | ||
| [textInputPlugin hideEditMenu]; | ||
| } | ||
| } | ||
|
|
||
| - (void)showShareViewController:(NSString*)content { | ||
| UIViewController* engineViewController = [_engine.get() viewController]; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -794,6 +794,7 @@ @interface FlutterTextInputView () | |
| // This is cleared at the start of each keyboard interaction. (Enter a character, delete a character | ||
| // etc) | ||
| @property(nonatomic, copy) NSString* temporarilyDeletedComposedCharacter; | ||
| @property(nonatomic, assign) CGRect editMenuTargetRect; | ||
|
|
||
| - (void)setEditableTransform:(NSArray*)matrix; | ||
| @end | ||
|
|
@@ -859,9 +860,44 @@ - (instancetype)initWithOwner:(FlutterTextInputPlugin*)textInputPlugin { | |
| } | ||
| } | ||
|
|
||
| if (@available(iOS 16.0, *)) { | ||
| _editMenuInteraction = [[UIEditMenuInteraction alloc] initWithDelegate:self]; | ||
| [self addInteraction:_editMenuInteraction]; | ||
| } | ||
|
|
||
| return self; | ||
| } | ||
|
|
||
| - (UIMenu*)editMenuInteraction:(UIEditMenuInteraction*)interaction | ||
| menuForConfiguration:(UIEditMenuConfiguration*)configuration | ||
| suggestedActions:(NSArray<UIMenuElement*>*)suggestedActions API_AVAILABLE(ios(16.0)) { | ||
| return [UIMenu menuWithChildren:suggestedActions]; | ||
|
Contributor
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. So in future milestones the framework could pass UIMenuElements like this?
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. Yes, the framework can send the items via
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. Custom actions (e.g. send email) can be a bit more involved (e.g. the engine would need to call back to the framework). |
||
| } | ||
|
|
||
| - (void)editMenuInteraction:(UIEditMenuInteraction*)interaction | ||
| willDismissMenuForConfiguration:(UIEditMenuConfiguration*)configuration | ||
| animator:(id<UIEditMenuInteractionAnimating>)animator | ||
| API_AVAILABLE(ios(16.0)) { | ||
| [self.textInputDelegate flutterTextInputView:self | ||
| willDismissEditMenuWithTextInputClient:_textInputClient]; | ||
| } | ||
|
|
||
| - (CGRect)editMenuInteraction:(UIEditMenuInteraction*)interaction | ||
| targetRectForConfiguration:(UIEditMenuConfiguration*)configuration API_AVAILABLE(ios(16.0)) { | ||
| return _editMenuTargetRect; | ||
| } | ||
|
|
||
| - (void)showEditMenuWithTargetRect:(CGRect)targetRect API_AVAILABLE(ios(16.0)) { | ||
| _editMenuTargetRect = targetRect; | ||
| UIEditMenuConfiguration* config = | ||
| [UIEditMenuConfiguration configurationWithIdentifier:nil sourcePoint:CGPointZero]; | ||
| [self.editMenuInteraction presentEditMenuWithConfiguration:config]; | ||
| } | ||
|
|
||
| - (void)hideEditMenu API_AVAILABLE(ios(16.0)) { | ||
| [self.editMenuInteraction dismissMenu]; | ||
| } | ||
|
|
||
| - (void)configureWithDictionary:(NSDictionary*)configuration { | ||
| NSDictionary* inputType = configuration[kKeyboardType]; | ||
| NSString* keyboardAppearance = configuration[kKeyboardAppearance]; | ||
|
|
@@ -1148,8 +1184,10 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { | |
| if (action == @selector(paste:)) { | ||
| // Forbid pasting images, memojis, or other non-string content. | ||
| return [UIPasteboard generalPasteboard].hasStrings; | ||
| } else if (action == @selector(copy:) || action == @selector(cut:) || | ||
| action == @selector(delete:)) { | ||
| return [self textInRange:_selectedTextRange].length > 0; | ||
| } | ||
|
|
||
| return [super canPerformAction:action withSender:sender]; | ||
| } | ||
|
|
||
|
|
@@ -2511,6 +2549,23 @@ - (void)takeKeyboardScreenshotAndDisplay { | |
| _keyboardViewContainer.frame = _keyboardRect; | ||
| } | ||
|
|
||
| - (BOOL)showEditMenu:(NSDictionary*)args API_AVAILABLE(ios(16.0)) { | ||
| if (!self.activeView.isFirstResponder) { | ||
|
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. @justinmc this is where I determine the menu is triggered by text input widgets vs non-text input widgets. I can also check |
||
| return NO; | ||
| } | ||
| NSDictionary<NSString*, NSNumber*>* encodedTargetRect = args[@"targetRect"]; | ||
| CGRect globalTargetRect = CGRectMake( | ||
| [encodedTargetRect[@"x"] doubleValue], [encodedTargetRect[@"y"] doubleValue], | ||
| [encodedTargetRect[@"width"] doubleValue], [encodedTargetRect[@"height"] doubleValue]); | ||
| CGRect localTargetRect = [self.hostView convertRect:globalTargetRect toView:self.activeView]; | ||
| [self.activeView showEditMenuWithTargetRect:localTargetRect]; | ||
| return YES; | ||
| } | ||
|
|
||
| - (void)hideEditMenu { | ||
| [self.activeView hideEditMenu]; | ||
| } | ||
|
|
||
| - (void)setEditableSizeAndTransform:(NSDictionary*)dictionary { | ||
| NSArray* transform = dictionary[@"transform"]; | ||
| [_activeView setEditableTransform:transform]; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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.
@justinmc Wanna double check before landing it:
For non-text input, if developers setup
contextMenuBuilderto use system menu, does it call this method? Does it crash or does it just NO-OP?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.
Currently it calls it, even if there is no active text input connection. The result I see is that nothing happens. I wonder if there's anything we can do to prevent that unnecessary call, let me look on the framework side...
To be clear, at least the only way to do this now is to directly use SystemContextMenuController. It's not currently possible to use SystemContextMenu without an EditableText.
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.
I don't think there's a good way for the framework to know whether or not there is an active text input connection at that point (in SystemContextMenuController). Maybe should the ContextMenu.showSystemContextMenu call indicate an error in the case where it doesn't succeed in showing the menu?
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.
I'm adding some docs to the framework PR about this.
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.
I think I can add a warning on the engine side.