iOS 13 UIActivityViewController automatically present previous VC after image saving

落爺英雄遲暮 提交于 2020-07-17 03:22:20

问题


I'm trying to implement “Save image to Library” function and then return back to the current view controller, but on a new iOS 13 it dismisses back to the view controller that presented the current one:

PHPhotoLibrary.requestAuthorization({(_ status: PHAuthorizationStatus) -> Void in })

let shareItems: Array = [newImg,"Hello"] as [Any]

let activityController = UIActivityViewController(activityItems: shareItems, applicationActivities: nil)

if UIDevice.current.userInterfaceIdiom == .pad {
    activityController.popoverPresentationController?.sourceView = saveButton
}

present(activityController, animated: true)

回答1:


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.




回答2:


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.




回答3:


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);
}



回答4:


Here is the way I worked around this bug for now. I create a fake view controller and push it onto the current stack. It seems that UIActivityTypeSaveToCameraRoll dismisses the top view controller on the stack, whereas the other options do not. I take care of dismissing the fake view controller in the completion block if the activity type is not complete and UIActivityTypeSaveToCameraRoll.

typeof(self) __weak weakSelf = self;

[self.activityViewController setCompletionHandler:^(NSString *activityType, BOOL completed) {
        if (activityType== UIActivityTypeSaveToCameraRoll && completed){
              weakSelf.activityViewController = nil;
        }
        else{
             [weakSelf dismissViewControllerAnimated:NO completion:nil];
             weakSelf.activityViewController = nil;       
        }
}];


UIViewController *fakeVC=[[UIViewController alloc] init];

[self presentViewController:fakeVC animated:NO completion:^{
       [fakeVC presentViewController:self.activityViewController animated:YES completion:nil];
}];



回答5:


- (UIWindow *)displayWindow
{
    if (!_displayWindow)
    {
        _displayWindow = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
        _displayWindow.rootViewController = [[UIViewController alloc] init];
    }
    return _displayWindow;
}

- (void)showActivityController
{
    UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[] applicationActivities:nil];
    activityViewController.completionWithItemsHandler = ^(UIActivityType __nullable activityType, BOOL completed, NSArray * __nullable returnedItems, NSError * __nullable activityError)
    {
        [UIApplication.sharedApplication.delegate.window makeKeyAndVisible];
    };

    [self.displayWindow makeKeyAndVisible];
    [self.displayWindow.rootViewController presentViewController:activityViewController animated:true completion:nil];
}

Make sure the _displayWindow is strong reference.




回答6:


I resolved this issue by setting the root view controller to the current window, I have no idea why it dismisses the current view controller. I noticed that while presenting the new view controller in iOS 13 it will present to the card stack up style and if I chose "Save Image" in UIActivityController then the current view controller (card) will be dismissed and show up the previous view controller.

guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
      let window = appDelegate.window else { return }
window.rootViewController = viewcontroller

I use this one instead of presenting view controller when I need to start a new story.

BTW, it depends on your requirements. In this case, I can dismiss the old unused one because no need to back, maybe you can use this instead of presenting a new scene.



来源:https://stackoverflow.com/questions/56903030/ios-13-uiactivityviewcontroller-automatically-present-previous-vc-after-image-sa

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!