Platform
share_plus: 13.2.0 (same code present in 12.x)
- iOS 26.3 – 26.5.2, iPhone (reproduced on iPhone 16e / 17 Pro / 17 Pro Max, simulator and physical devices)
- Flutter 3.44.4
Description
In FPPSharePlusPlugin.m, popoverPresentationController.sourceView is assigned unconditionally, regardless of UIUserInterfaceIdiom:
activityViewController.popoverPresentationController.sourceView = topViewController.view;
if (!CGRectIsEmpty(origin)) {
activityViewController.popoverPresentationController.sourceRect = origin;
}
There is no way to opt out from Dart: even when the caller passes no sharePositionOrigin, sourceView is still set.
On iPhone this used to be harmless, but on iOS 26 a non-nil sourceView routes UIActivityViewController into the popover/anchored presentation path (compact sheet anchored to the Flutter view). When the user dismisses that sheet interactively (swipe down — most reliably after visiting a sub-screen such as AirDrop and coming back), the dismissal can end up in a broken state:
completionWithItemsHandler is never called → the Future returned by SharePlus.share() never completes, and any await/finally after it is dead.
- The
UIActivityViewController can stay presented with isBeingDismissed == true indefinitely (interrupted interactive dismissal never finishes), or stay presented with its view already detached from the window.
- Touch delivery inside the app degrades: taps in part of the Flutter UI (in our case the bottom sheet under the share origin) stop being delivered until the presentation stack is rebuilt (e.g., by pushing/popping any route). Instrumentation shows events dying before reaching gesture handlers while the widget tree is healthy.
Steps to reproduce
- iPhone with iOS 26.x, any app calling
SharePlus.instance.share(ShareParams(uri: ...)) (no sharePositionOrigin).
- Open the share sheet, optionally enter the AirDrop sub-screen and come back.
- Dismiss the sheet with a swipe-down gesture.
- Observe: the
share() Future often never completes; in a fraction of runs taps around the share origin stop working.
The race is timing-sensitive (roughly 1 in 3 attempts in our testing), the AirDrop detour makes it much more reliable.
Expected
sourceView/popover configuration only applies on iPad (where sharePositionOrigin is genuinely required), or is at least skippable from Dart.
completionWithItemsHandler fires (with completed == false) for interrupted interactive dismissals so the returned Future always completes.
Workaround we ship (vendored fork)
Wrapping the popover configuration in an idiom check fixes the iPhone path while keeping iPad behavior intact:
BOOL isIpad = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad);
if (isIpad) {
activityViewController.popoverPresentationController.sourceView =
topViewController.view;
if (!CGRectIsEmpty(origin)) {
activityViewController.popoverPresentationController.sourceRect = origin;
}
}
The hung-completion part still needs an app-level watchdog on iOS 26 even with this patch (the OS sometimes drops the completion for interrupted dismissals), but the touch-delivery breakage no longer reproduces with sourceView unset on iPhone.
Happy to send a PR with the idiom guard if that direction works for the maintainers.
Platform
share_plus: 13.2.0 (same code present in 12.x)Description
In
FPPSharePlusPlugin.m,popoverPresentationController.sourceViewis assigned unconditionally, regardless ofUIUserInterfaceIdiom:There is no way to opt out from Dart: even when the caller passes no
sharePositionOrigin,sourceViewis still set.On iPhone this used to be harmless, but on iOS 26 a non-nil
sourceViewroutesUIActivityViewControllerinto the popover/anchored presentation path (compact sheet anchored to the Flutter view). When the user dismisses that sheet interactively (swipe down — most reliably after visiting a sub-screen such as AirDrop and coming back), the dismissal can end up in a broken state:completionWithItemsHandleris never called → theFuturereturned bySharePlus.share()never completes, and anyawait/finallyafter it is dead.UIActivityViewControllercan staypresentedwithisBeingDismissed == trueindefinitely (interrupted interactive dismissal never finishes), or stay presented with its view already detached from the window.Steps to reproduce
SharePlus.instance.share(ShareParams(uri: ...))(nosharePositionOrigin).share()Future often never completes; in a fraction of runs taps around the share origin stop working.The race is timing-sensitive (roughly 1 in 3 attempts in our testing), the AirDrop detour makes it much more reliable.
Expected
sourceView/popover configuration only applies on iPad (wheresharePositionOriginis genuinely required), or is at least skippable from Dart.completionWithItemsHandlerfires (withcompleted == false) for interrupted interactive dismissals so the returned Future always completes.Workaround we ship (vendored fork)
Wrapping the popover configuration in an idiom check fixes the iPhone path while keeping iPad behavior intact:
The hung-completion part still needs an app-level watchdog on iOS 26 even with this patch (the OS sometimes drops the completion for interrupted dismissals), but the touch-delivery breakage no longer reproduces with
sourceViewunset on iPhone.Happy to send a PR with the idiom guard if that direction works for the maintainers.