Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#import <UIKit/UIKit.h>

#include "flutter/fml/logging.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h"

Expand Down Expand Up @@ -154,10 +156,38 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {

- (void)showShareViewController:(NSString*)content {
UIViewController* engineViewController = [_engine.get() viewController];

NSArray* itemsToShare = @[ content ?: [NSNull null] ];
UIActivityViewController* activityViewController =
[[[UIActivityViewController alloc] initWithActivityItems:itemsToShare
applicationActivities:nil] autorelease];

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
// On iPad, the share screen is presented in a popover view, and requires a
// sourceView and sourceRect
FlutterTextInputPlugin* _textInputPlugin = [_engine.get() textInputPlugin];
UITextRange* range = _textInputPlugin.textInputView.selectedTextRange;

// firstRectForRange cannot be used here as it's current implementation does
// not always return the full rect of the range.
CGRect firstRect = [(FlutterTextInputView*)_textInputPlugin.textInputView
caretRectForPosition:(FlutterTextPosition*)range.start];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

take a look at firstRectForRange which takes the range directly.

Copy link
Contributor Author

@LouiseHsu LouiseHsu Nov 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesnt, or at least not usably. After transforming it gives me the coordinates for the top right corner of the first character, which is right, but the size is super, super off.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a note here that we can't use firstRectForRange API since it's current implementation doesn't always return the full rect of the range.

CGRect transformedFirstRect = [(FlutterTextInputView*)_textInputPlugin.textInputView
localRectFromFrameworkTransform:firstRect];
CGRect lastRect = [(FlutterTextInputView*)_textInputPlugin.textInputView
caretRectForPosition:(FlutterTextPosition*)range.end];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iirc range.end is exclusive.

CGRect transformedLastRect = [(FlutterTextInputView*)_textInputPlugin.textInputView
localRectFromFrameworkTransform:lastRect];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this localRectFromFrameworkTransform do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you get global coordinates from local ones

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. The naming is a bit weird and sounds like the opposite.


activityViewController.popoverPresentationController.sourceView = engineViewController.view;
// In case of RTL Language, get the minimum x coordinate
activityViewController.popoverPresentationController.sourceRect =
CGRectMake(fmin(transformedFirstRect.origin.x, transformedLastRect.origin.x),
transformedFirstRect.origin.y,
abs(transformedLastRect.origin.x - transformedFirstRect.origin.x),
transformedFirstRect.size.height);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this won't work for RTL language. possibly crash due to negative width

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, would it help if i just wrapped in abs() ? I dont know what rtl language is

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abs() should work. but please try it out

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RTL = right-to-left, e.g. arabic/hebrew


[engineViewController presentViewController:activityViewController animated:YES completion:nil];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,44 @@ - (void)testShareScreenInvoked {
[self waitForExpectationsWithTimeout:1 handler:nil];
}

- (void)testShareScreenInvokedOnIPad {
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
[engine runWithEntrypoint:nil];
std::unique_ptr<fml::WeakNSObjectFactory<FlutterEngine>> _weakFactory =
std::make_unique<fml::WeakNSObjectFactory<FlutterEngine>>(engine);

XCTestExpectation* presentExpectation =
[self expectationWithDescription:@"Share view controller presented on iPad"];

FlutterViewController* engineViewController = [[FlutterViewController alloc] initWithEngine:engine
nibName:nil
bundle:nil];
FlutterViewController* mockEngineViewController = OCMPartialMock(engineViewController);
OCMStub([mockEngineViewController
presentViewController:[OCMArg isKindOfClass:[UIActivityViewController class]]
animated:YES
completion:nil]);

id mockTraitCollection = OCMClassMock([UITraitCollection class]);
OCMStub([mockTraitCollection userInterfaceIdiom]).andReturn(UIUserInterfaceIdiomPad);

FlutterPlatformPlugin* plugin =
[[FlutterPlatformPlugin alloc] initWithEngine:_weakFactory->GetWeakNSObject()];
FlutterPlatformPlugin* mockPlugin = OCMPartialMock(plugin);

FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"Share.invoke"
arguments:@"Test"];
FlutterResult result = ^(id result) {
OCMVerify([mockEngineViewController
presentViewController:[OCMArg isKindOfClass:[UIActivityViewController class]]
animated:YES
completion:nil]);
[presentExpectation fulfill];
};
[mockPlugin handleMethodCall:methodCall result:result];
[self waitForExpectationsWithTimeout:1 handler:nil];
}

- (void)testClipboardHasCorrectStrings {
[UIPasteboard generalPasteboard].string = nil;
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ FLUTTER_DARWIN_EXPORT
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithOwner:(FlutterTextInputPlugin*)textInputPlugin NS_DESIGNATED_INITIALIZER;

// TODO(louisehsu): These are being exposed to support Share in FlutterPlatformPlugin
// Consider moving that feature into FlutterTextInputPlugin to avoid exposing extra methods
- (CGRect)localRectFromFrameworkTransform:(CGRect)incomingRect;
- (CGRect)caretRectForPosition:(UITextPosition*)position;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are you exposing these APIs?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a TODO here, explaining that these are exposed to be used for your feature, but we should consider moving your feature inside this plugin so we don't have to expose it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you file an issue to further investigate this?

@end

@interface UIView (FindFirstResponder)
Expand Down