iOS 13的UIActivityViewController在图片保存后自动呈现之前的视图控制器

34

我正在尝试实现“保存图像到相册”功能,然后返回到当前的视图控制器,但在新的 iOS 13 上,它会关闭当前的视图控制器并返回到前一个呈现它的视图控制器:

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)

3
有趣的是,我注意到只有在选择保存图片时才会出现这个 bug。如果选择其他选项,则似乎会返回到正确的视图控制器。 - Rob
1
我遇到了相同的问题。这甚至发生在运行 iOS 13 的设备上,使用 Xcode 10 构建的应用程序上。 - bdmontz
1
也可以确认。这个问题有解决了吗? - Diarrhio
1
一样的问题。Xcode 11.1 运行在 iOS 13.1.3 上。尝试了下面的所有解决方案都没有成功。 - Marta Rodriguez
4
这是一个非常好的漏洞! - matt
显示剩余5条评论
9个回答

12

@KDP的解决方案的Swift版本:

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 要么在活动完成时被解除,要么我们需要在完成时解除它。

太好了,这个完美无缺,谢谢!顺便说一下,在iOS 14上出现了与iOS 13相同的问题。 - sabiland

7
我可以确认,在iOS 13.3.1中仍存在此错误。以下解决方法是franze's solution的Swift版本。我更喜欢这种方法,因为它不对视图控制器层次结构做出任何进一步的假设,并且不使用方法交换。
在iOS 12及更早版本中,使用此额外的UIWindow会破坏UIActivityViewController取消按钮,因此我添加了一个操作系统版本的检查。
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)
  }
}

更新:显然,这个解决方案在iPad上不可靠。在iPad上,UIActivityViewController的呈现方式似乎不同,一旦它出现在屏幕上,就无法注册触摸事件,从而导致应用程序冻结。


1
经过进一步测试,我注意到我的原始解决方案在iOS 12及更早版本上无法正确工作 - 请参见我的更新答案。 - Theo
@Theo 我猜这个功能是实现在插件的 .m 文件中,但我找不到它的位置,我尝试绕过 getTopMostViewController 函数,但它只会破坏它。你能展示一下你实现这个功能来覆盖标准视图的位置吗?非常感谢。 - troggy69
@troggy69 在视图控制器中实现呈现 UIActivityViewController 的方法。然后,不要调用 present(activityViewController, animated: true),而是调用 showActivityViewController() - Theo
@Theo 不确定这对你有帮助,但我发现iPad的问题是它显示在视图后面,所以在打开时,你需要添加(根据你的变量)ref.show(); 在我的情况下,我必须先添加 ref.hide()。我怀疑这不是你需要的,但可能会提供一些见解。 - troggy69
我可以确认这个错误在iOS 14 beta 3中仍然存在。我已经提交了一个错误报告FB8111145。 - Theo
显示剩余7条评论

4
我生成了以下的猴子补丁(在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);
}

3
- (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];
}

确保_displayWindow是强引用。


我实现了一个等效的解决方案,目前它已经运行良好。 - Drew

3

目前,我是通过以下方式解决该bug的。我创建一个虚拟视图控制器,并将其推入当前堆栈中。似乎UIActivityTypeSaveToCameraRoll会关闭堆栈中的顶部视图控制器,而其他选项则不会。如果活动类型不是complete和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];
}];

2

我通过将根视图控制器设置为当前窗口来解决了这个问题,我不知道为什么它会关闭当前的视图控制器。我注意到,在iOS 13中呈现新的视图控制器时,它会以卡堆叠样式呈现,并且如果我在UIActivityController中选择“保存图像”,则当前的视图控制器(卡)将被关闭并显示出先前的视图控制器。

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

当需要开始新的故事时,我使用这个而不是呈现视图控制器。

顺便说一下,这取决于您的要求。在这种情况下,我可以关闭旧的未使用的故事,因为不需要后退,也许您可以使用这个来代替呈现新场景。


0

我曾经遇到过同样的问题,因此我改变了viewcontroller的显示方式。

调用分享按钮的viewcontroller对我来说存在问题。

我之前是这样调用分享按钮的viewcontoller的: enter image description here self.present(.......) 然后我进行了更改

self.view.window.rootviewcontroller = 分享按钮的viewcontoller

然后问题就解决了。


您的回答目前写得不够清晰,请编辑并添加更多细节以帮助其他人理解它与问题的关系。您可以在帮助中心找到更多编写良好答案的信息。 - Vikram Parimi

0

-1
对于那些在iPad上遇到屏幕冻结问题的人,这里有一个简单的解决方案。它也适用于iPhone。
func shareItems(_ sharedItems: [Any]) {

        let activityViewController = UIActivityViewController(activityItems: sharedItems, applicationActivities: nil)
        if let popoverController = activityViewController.popoverPresentationController {
            popoverController.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height / 2, width: 0, height: 0)
            popoverController.sourceView = self.view
            popoverController.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
        }

        self.present(activityViewController, animated: true, completion: nil)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接