Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 2bd675c

Browse files
authored
Fix Share Screen Crash on iPad (#48220)
Fixes flutter/flutter#138550 ![Simulator Screenshot - iPad Air (5th generation) - 2023-11-21 at 03 33 37](https://github.com/flutter/engine/assets/36148254/15e10e43-816b-43b1-a5ab-75c8add90899)
1 parent 0419b81 commit 2bd675c

File tree

3 files changed

+72
-0
lines changed

3 files changed

+72
-0
lines changed

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#import <UIKit/UIKit.h>
1111

1212
#include "flutter/fml/logging.h"
13+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
14+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
1315
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
1416
#import "flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h"
1517

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

155157
- (void)showShareViewController:(NSString*)content {
156158
UIViewController* engineViewController = [_engine.get() viewController];
159+
157160
NSArray* itemsToShare = @[ content ?: [NSNull null] ];
158161
UIActivityViewController* activityViewController =
159162
[[[UIActivityViewController alloc] initWithActivityItems:itemsToShare
160163
applicationActivities:nil] autorelease];
164+
165+
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
166+
// On iPad, the share screen is presented in a popover view, and requires a
167+
// sourceView and sourceRect
168+
FlutterTextInputPlugin* _textInputPlugin = [_engine.get() textInputPlugin];
169+
UITextRange* range = _textInputPlugin.textInputView.selectedTextRange;
170+
171+
// firstRectForRange cannot be used here as it's current implementation does
172+
// not always return the full rect of the range.
173+
CGRect firstRect = [(FlutterTextInputView*)_textInputPlugin.textInputView
174+
caretRectForPosition:(FlutterTextPosition*)range.start];
175+
CGRect transformedFirstRect = [(FlutterTextInputView*)_textInputPlugin.textInputView
176+
localRectFromFrameworkTransform:firstRect];
177+
CGRect lastRect = [(FlutterTextInputView*)_textInputPlugin.textInputView
178+
caretRectForPosition:(FlutterTextPosition*)range.end];
179+
CGRect transformedLastRect = [(FlutterTextInputView*)_textInputPlugin.textInputView
180+
localRectFromFrameworkTransform:lastRect];
181+
182+
activityViewController.popoverPresentationController.sourceView = engineViewController.view;
183+
// In case of RTL Language, get the minimum x coordinate
184+
activityViewController.popoverPresentationController.sourceRect =
185+
CGRectMake(fmin(transformedFirstRect.origin.x, transformedLastRect.origin.x),
186+
transformedFirstRect.origin.y,
187+
abs(transformedLastRect.origin.x - transformedFirstRect.origin.x),
188+
transformedFirstRect.size.height);
189+
}
190+
161191
[engineViewController presentViewController:activityViewController animated:YES completion:nil];
162192
}
163193

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,44 @@ - (void)testShareScreenInvoked {
166166
[self waitForExpectationsWithTimeout:1 handler:nil];
167167
}
168168

169+
- (void)testShareScreenInvokedOnIPad {
170+
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
171+
[engine runWithEntrypoint:nil];
172+
std::unique_ptr<fml::WeakNSObjectFactory<FlutterEngine>> _weakFactory =
173+
std::make_unique<fml::WeakNSObjectFactory<FlutterEngine>>(engine);
174+
175+
XCTestExpectation* presentExpectation =
176+
[self expectationWithDescription:@"Share view controller presented on iPad"];
177+
178+
FlutterViewController* engineViewController = [[FlutterViewController alloc] initWithEngine:engine
179+
nibName:nil
180+
bundle:nil];
181+
FlutterViewController* mockEngineViewController = OCMPartialMock(engineViewController);
182+
OCMStub([mockEngineViewController
183+
presentViewController:[OCMArg isKindOfClass:[UIActivityViewController class]]
184+
animated:YES
185+
completion:nil]);
186+
187+
id mockTraitCollection = OCMClassMock([UITraitCollection class]);
188+
OCMStub([mockTraitCollection userInterfaceIdiom]).andReturn(UIUserInterfaceIdiomPad);
189+
190+
FlutterPlatformPlugin* plugin =
191+
[[FlutterPlatformPlugin alloc] initWithEngine:_weakFactory->GetWeakNSObject()];
192+
FlutterPlatformPlugin* mockPlugin = OCMPartialMock(plugin);
193+
194+
FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"Share.invoke"
195+
arguments:@"Test"];
196+
FlutterResult result = ^(id result) {
197+
OCMVerify([mockEngineViewController
198+
presentViewController:[OCMArg isKindOfClass:[UIActivityViewController class]]
199+
animated:YES
200+
completion:nil]);
201+
[presentExpectation fulfill];
202+
};
203+
[mockPlugin handleMethodCall:methodCall result:result];
204+
[self waitForExpectationsWithTimeout:1 handler:nil];
205+
}
206+
169207
- (void)testClipboardHasCorrectStrings {
170208
[UIPasteboard generalPasteboard].string = nil;
171209
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ FLUTTER_DARWIN_EXPORT
163163
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
164164
- (instancetype)initWithOwner:(FlutterTextInputPlugin*)textInputPlugin NS_DESIGNATED_INITIALIZER;
165165

166+
// TODO(louisehsu): These are being exposed to support Share in FlutterPlatformPlugin
167+
// Consider moving that feature into FlutterTextInputPlugin to avoid exposing extra methods
168+
- (CGRect)localRectFromFrameworkTransform:(CGRect)incomingRect;
169+
- (CGRect)caretRectForPosition:(UITextPosition*)position;
166170
@end
167171

168172
@interface UIView (FindFirstResponder)

0 commit comments

Comments
 (0)