问题
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