iOS 13 UIActivityViewController automatically present previous VC after image saving

Swift version of @KDP's solution:

let fakeViewController = TransparentViewController()
fakeViewController.modalPresentationStyle = .overFullScreen

activityViewController.completionWithItemsHandler = { [weak fakeViewController] _, _, _, _ in
    if let presentingViewController = fakeViewController?.presentingViewController {
        presentingViewController.dismiss(animated: false, completion: nil)
    } else {
        fakeViewController?.dismiss(animated: false, completion: nil)
    }
}
present(fakeViewController, animated: true) { [weak fakeViewController] in
    fakeViewController?.present(activityViewController, animated: true, completion: nil)
}

fakeViewController gets either dismissed by activity completion, or we need to dismiss it in completion.


I can confirm that this bug is still present in iOS 13.3.1. The following workaround is a Swift version of franze's solution. I prefer this approach, as it doesn't make any further assumptions on the view controller hierarchy and doesn't use method swizzling.

Using this additional UIWindow breaks the Cancel button of the UIActivityViewController on iOS 12 and earlier, so I added a check for the OS version.

private let activityWindow: UIWindow = {
  let window = UIWindow(frame: UIScreen.main.bounds)
  window.rootViewController = UIViewController()
  return window
}()

func showActivityController() {
  let activityViewController = UIActivityViewController(/* ... */)
  activityViewController.completionWithItemsHandler = {
     // ...
     UIApplication.shared.delegate?.window??.makeKeyAndVisible()       
  }

  // Use this workaround only on iOS 13
  if ProcessInfo.processInfo.operatingSystemVersion.majorVersion == 13 {
    activityWindow.makeKeyAndVisible()
    activityWindow.rootViewController?.present(activityViewController, animated: true)
  } else {
    present(activityViewController, animated: true)
  }
}

Update: Apparently, this solution doesn't work reliably on iPads. It looks like on iPad the UIActivityViewController is presented differently and as soon as it's visible on screen, no touch events are registered, effectively freezing the app.


I generated following monkey patch (Checked by iOS 13.1.2)

- (void)export {

  //
  // ... Generate Your Activity Items ...
  //

  UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:activityItems
                                                                                           applicationActivities:nil
                                                                                                   completeBlock:^(NSError *activityError, BOOL completed) {
                                                                                                       // Swizzling Dismiss Method
                                                                                                       [[self class]   switchInstanceMethodFrom:@selector(dismissViewControllerAnimated:completion:) To:@selector(lockedDismissViewControllerAnimated:completion:)];
                                                                                                   }
                                                                                ];
  // Swizzling Dismiss Method
  [[self class] switchInstanceMethodFrom:@selector(dismissViewControllerAnimated:completion:) To:@selector(lockedDismissViewControllerAnimated:completion:)];
  [self presentViewController:activityViewController animated:YES completion:nil];
}

- (void)lockedDismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
    if ([self presentedViewController]) {
        [self lockedDismissViewControllerAnimated:flag completion:completion];
    }
}

// from http://qiita.com/paming/items/25eaf89e4f448ab05752
+(void)switchInstanceMethodFrom:(SEL)from To:(SEL)to
{
    Method fromMethod = class_getInstanceMethod(self,from);
    Method toMethod   = class_getInstanceMethod(self,to  );
    method_exchangeImplementations(fromMethod, toMethod);
}