iOS 13 UIActivityViewController 保存图片后自动呈现上一张VC
iOS 13 UIActivityViewController automatically present previous VC after image saving
我正在尝试实现“将图像保存到库”功能,然后 return 返回到当前视图控制器,但是在新的 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)
我通过将根视图控制器设置为当前 window 解决了这个问题,我不知道为什么它会关闭当前视图控制器。我注意到,在 iOS 13 中呈现新的视图控制器时,它将以卡片堆叠样式呈现,如果我在 UIActivityController 中选择 "Save Image",则当前视图控制器(卡片)将被关闭并显示前一个视图控制器。
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let window = appDelegate.window else { return }
window.rootViewController = viewcontroller
当我需要开始一个新故事时,我使用这个而不是呈现视图控制器。
顺便说一句,这取决于您的要求。在这种情况下,我可以关闭旧的未使用的,因为不需要返回,也许您可以使用它来代替呈现新场景。
这是我目前解决此错误的方法。我创建了一个假视图控制器并将其推送到当前堆栈。似乎 UIActivityTypeSaveToCameraRoll 取消了堆栈上的顶视图控制器,而其他选项则没有。如果 activity 类型不完整且 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];
}];
我生成了以下猴子补丁(由 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);
}
- (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
是强引用。
Swift @KDP 解决方案的版本:
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
要么被 activity 完成解雇,要么我们需要在完成时解雇它。
我可以确认这个错误在 iOS 13.3.1 中仍然存在。以下解决方法是 的 Swift 版本。我更喜欢这种方法,因为它不对视图控制器层次结构做任何进一步的假设,也不使用方法调配。
使用这个额外的 UIWindow
会破坏 iOS 12 和更早版本上 UIActivityViewController
的 Cancel 按钮,所以我添加了一个检查OS 版本。
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
的显示方式有所不同,只要它在屏幕上可见,就不会注册任何触摸事件,从而有效地冻结了应用程序。
对于那些在 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)
}
似乎已在 iOS 14 中修复。对于旧版本,我发现通过使用空实现覆盖 dismiss(animated:)
方法更容易解决。参见
我正在尝试实现“将图像保存到库”功能,然后 return 返回到当前视图控制器,但是在新的 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)
我通过将根视图控制器设置为当前 window 解决了这个问题,我不知道为什么它会关闭当前视图控制器。我注意到,在 iOS 13 中呈现新的视图控制器时,它将以卡片堆叠样式呈现,如果我在 UIActivityController 中选择 "Save Image",则当前视图控制器(卡片)将被关闭并显示前一个视图控制器。
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let window = appDelegate.window else { return }
window.rootViewController = viewcontroller
当我需要开始一个新故事时,我使用这个而不是呈现视图控制器。
顺便说一句,这取决于您的要求。在这种情况下,我可以关闭旧的未使用的,因为不需要返回,也许您可以使用它来代替呈现新场景。
这是我目前解决此错误的方法。我创建了一个假视图控制器并将其推送到当前堆栈。似乎 UIActivityTypeSaveToCameraRoll 取消了堆栈上的顶视图控制器,而其他选项则没有。如果 activity 类型不完整且 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];
}];
我生成了以下猴子补丁(由 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);
}
- (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
是强引用。
Swift @KDP 解决方案的版本:
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
要么被 activity 完成解雇,要么我们需要在完成时解雇它。
我可以确认这个错误在 iOS 13.3.1 中仍然存在。以下解决方法是
使用这个额外的 UIWindow
会破坏 iOS 12 和更早版本上 UIActivityViewController
的 Cancel 按钮,所以我添加了一个检查OS 版本。
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
的显示方式有所不同,只要它在屏幕上可见,就不会注册任何触摸事件,从而有效地冻结了应用程序。
对于那些在 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)
}
似乎已在 iOS 14 中修复。对于旧版本,我发现通过使用空实现覆盖 dismiss(animated:)
方法更容易解决。参见