UIActivityViewController dismisses the presenting view controller after sharing files.
this is happening in iOS 13+ only. Is there any permanent solution for this?
Others apps seem to have this issue too after updating to iOS 13.
class VC : UIViewController {
#IBAction func moveFiles(_ sender: UIButton) {
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "Move", style: .default, handler: { action in
let activityController = UIActivityViewController(activityItems: urls, applicationActivities: nil)
if (UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad) {
activityController.popoverPresentationController?.sourceRect = sender.frame
activityController.popoverPresentationController?.sourceView = sender.superview
}
self.present(activityController, animated: true, completion: nil)
}))
}
}
Here is the work around for your issue.
let tempController = TransparentViewController()
tempController.modalPresentationStyle = .overFullScreen
activityViewController.completionWithItemsHandler = { [weak tempController] _, _, _, _ in
if let presentingViewController = tempController?.presentingViewController {
presentingViewController.dismiss(animated: false, completion: nil)
} else {
tempController?.dismiss(animated: false, completion: nil)
}
}
present(tempController, animated: true) { [weak tempController] in
tempController?.present(activityViewController, animated: true, completion: nil)
}
Found similar question with solution which helps me. For iOS 13 show UIActivityViewController in another UIWindow
Stackoverflow answer
Seems it's fixed in iOS 14.4
For older iOS versions I found easier workaround. Override dismiss(animated:completion:) with empty implementation so it won't be dismissing itself automatically.
However, you can still dismiss this VC using super.dismiss(animated:completion).
E.g
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// do nothing to workaround bug - automatically dimisses this VC after saveToCameraRoll activity was performed
// call super.dismiss(animated:completion:) in order to really dismiss this VC
// seems fixed in iOS 14.4
}
...
#objc private func didTapCloseButton(_ sender: UIButton) {
super.dismiss(animated: true) // calling parent class implementation
}
I have same issue right now on iOS target 14.1.
I made my solution based on answers I found.
final class ShareViewController: UIViewController {
private let activityItems: [Any]
private let applicationActivities: [UIActivity]?
// Same looking initializer as UIActivityViewController has
init(activityItems: [Any], applicationActivities: [UIActivity]? = nil) {
self.activityItems = activityItems
self.applicationActivities = applicationActivities
super.init(nibName: nil, bundle: nil)
// Make transparent and covering entire screen
view.backgroundColor = .clear
modalPresentationStyle = .overCurrentContext
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Present UIActivityViewController here
presentShareSheet()
}
fileprivate func presentShareSheet() {
let shareSheet = UIActivityViewController(activityItems: activityItems,
applicationActivities: applicationActivities)
shareSheet.completionWithItemsHandler = { [weak self] _, _, _, _ in
// This is necessary to dismiss parent VC
self?.dismiss(animated: false)
}
self.present(shareSheet, animated: true)
}
}
And use it like UIActivityViewController but presenting it without animations
#objc private func shareImage() {
guard let image = imageView.image else { return }
let shareVC = ShareViewController(activityItems: [image])
present(shareVC, animated: false)
}
Related
I am using the following code to present a UIActivityViewController:
func share(content: Any, from viewController: UIViewController) {
let activityViewController = UIActivityViewController(
activityItems: ["TEST"],
applicationActivities: []
)
DispatchQueue.main.async {
viewController.present(activityViewController, animated: true)
}
}
Try to put all in this extension:
extension UIViewController {
func share2(vc: UIViewController, content: Any) {
let text = "TEST"
let activityViewController: UIActivityViewController = UIActivityViewController(activityItems: [text], applicationActivities: nil)
DispatchQueue.main.async {
self.present(activityViewController, animated: true)
}
}
}
now call the extension func wherever you want like this:
#objc func share() {
share2(vc: self, content: yourContent)
}
I have been using EasyAnimation which advices for enabling it in AppDelegate. That was the actual issue.
When a user purchased completion handler notify me and dismiss viewController. However, I want to display/show an alert to the user after viewController dismissed. At the moment when I step through in the debugger, it goes through the code but the alert isn't being shown. Still getting inbuilt in apple one that says All set. Is there a way I can display my alert after dismissing the viewController.
override func viewWillDisappear(_ pAnimated: Bool) {
super.viewWillDisappear(pAnimated)
self.notifyForUserHasPurchasedProduct {
self.presentingViewController?.dismiss(animated: true, completion: {
UIAlertController.bs_showAlertFrom(self, title: "AppName", message: "Thank you. Your purchase was successful")
})
}
}
You need to call self.present(alert, animated: true) to show alert. When ViewController self is not present, you need to change code to presentedViewController.present(alert, animated: true)
I have builded some functions:
extension UIViewController {
func topMostViewController() -> UIViewController {
if let presented = self.presentedViewController {
return presented.topMostViewController()
}
if let navigation = self as? UINavigationController {
return navigation.visibleViewController?.topMostViewController() ?? navigation
}
if let tab = self as? UITabBarController {
return tab.selectedViewController?.topMostViewController() ?? tab
}
return self
}
}
func getRootController () -> UIViewController { // function in global scope
return (UIApplication.shared.delegate?.window!!.rootViewController)!
}
And then use them like here:
override func viewWillDisappear(_ pAnimated: Bool) {
super.viewWillDisappear(pAnimated)
self.notifyForUserHasPurchasedProduct {
self.presentingViewController?.dismiss(animated: true, completion: {
let alert = UIAlertController(title: "AppName", message: "Thank you. Your purchase was successful", preferredStyle: .alert)
let topC = getRootController().topMostViewController()
topC.present(alert, animated: true, completion: nil)
})
}
}
After spending all day (>12 hours) trying to isolate a bug in 13 lines of mind-bogglingly generic code, I have come to the dubious conclusion that there must be a bug in the current iteration of CNContactPickerViewController, in iOS 9.2.
Simply copy+paste this ViewController and link the invite action to a button.
The bug is that MFMessageComposeViewController dismisses itself immediately.
If anybody knows what to do with this, do share?
import UIKit
import MessageUI
import ContactsUI
class ViewController: UIViewController, MFMessageComposeViewControllerDelegate, CNContactPickerDelegate {
let contactPickerVC = CNContactPickerViewController()
let messageVC = MFMessageComposeViewController()
override func viewDidLoad() {
super.viewDidLoad()
contactPickerVC.delegate = self
}
func contactPicker(picker: CNContactPickerViewController, didSelectContact contact: CNContact) {
if let phoneNumberValue = contact.phoneNumbers.first?.value as? CNPhoneNumber {
if let phoneNumber = phoneNumberValue.valueForKey("digits") as? String {
// Configure message ViewController
messageVC.messageComposeDelegate = self
messageVC.recipients = [phoneNumber]
messageVC.body = "Yoyoyo"
picker.presentViewController(messageVC, animated: true, completion: nil)
}
}
}
func messageComposeViewController(controller: MFMessageComposeViewController, didFinishWithResult result: MessageComposeResult) {
controller.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func invite(sender: AnyObject) {
presentViewController(contactPickerVC, animated: true, completion: nil)
}
}
I got it working by dismissing the pickerVC and changing the controller which presents the messageVC!
Insert (before the messageVC config lines):
picker.dismissViewControllerAnimated(true, completion: nil)
Replace
picker.presentViewController(messageVC, animated: true, completion: nil)
with
presentViewController(messageVC, animated: true, completion: nil)
I have trouble to display my UIAlertController because I'm trying to show it in a Class which is not an ViewController.
I already tried adding it:
var alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: .Alert)
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)
Which is not working...
I didn't find any solution that worked for me yet.
I wrote this extension over UIAlertController to bring back show().
It uses recursion to find the current top view controller:
extension UIAlertController {
func show() {
present(animated: true, completion: nil)
}
func present(animated: Bool, completion: (() -> Void)?) {
if let rootVC = UIApplication.shared.keyWindow?.rootViewController {
presentFromController(controller: rootVC, animated: animated, completion: completion)
}
}
private func presentFromController(controller: UIViewController, animated: Bool, completion: (() -> Void)?) {
if
let navVC = controller as? UINavigationController,
let visibleVC = navVC.visibleViewController
{
presentFromController(controller: visibleVC, animated: animated, completion: completion)
} else if
let tabVC = controller as? UITabBarController,
let selectedVC = tabVC.selectedViewController
{
presentFromController(controller: selectedVC, animated: animated, completion: completion)
} else if let presented = controller.presentedViewController {
presentFromController(controller: presented, animated: animated, completion: completion)
} else {
controller.present(self, animated: animated, completion: completion);
}
}
}
Now it's as easy as:
var alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: .Alert)
alertController.show()
This should work.
UIApplication.sharedApplication().windows[0].rootViewController?.presentViewController(...)
Create a helper function that you call from the current view controller and pass the current view controller as a parameter:
func showAlertInVC(
viewController: UIViewController,
title: String,
message: String)
{
//Code to create an alert controller and display it in viewController
}
If you solution is not working it probably because of there is no window at that moment. I had the same problem when I was trying to show alert view in application:DidFinishLoadingWithOptions method. In this case my solution was to check if root view controller is available, and if it's not, then add notification for UIApplicationDidBecomeActiveNotification
NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationDidBecomeActiveNotification,
object: nil,
queue: NSOperationQueue.mainQueue()) {
(_) in
//show your alert by using root view controller
//remove self from observing
}
}
I have the following IBActions on my navigation bar
#IBAction func logoutPressed(sender: AnyObject) {
SweetAlert().showAlert("Are you sure?", subTitle: "Do you really want to logout?", style: AlertStyle.Warning, buttonTitle:"Cancel", buttonColor:UIColorFromRGB(0xD0D0D0) , otherButtonTitle: "Yep", otherButtonColor: UIColorFromRGB(0xDD6B55)) { (isOtherButton) -> Void in
if isOtherButton == false {
let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64( Double(NSEC_PER_SEC) * 4.0 ))
SwiftSpinner.show("Logging out")
dispatch_after(popTime, dispatch_get_main_queue(), {
PFUser.logOut()
self.performSegueWithIdentifier("logoutSegue", sender: nil)
SwiftSpinner.hide()
})
}
else {
}
}
}
//OPTIONS MENU
#IBAction func optionsPressed(sender: AnyObject) {
let alert = SCLAlertView()
alert.addButton("Submit Feedback"){
var subjectText = "feedback"
var toRecipient = ["some email address"]
var mc:MFMailComposeViewController = MFMailComposeViewController()
mc.mailComposeDelegate = self
mc.setSubject(subjectText)
mc.setToRecipients(toRecipient)
self.presentViewController(mc, animated: true, completion: nil)
}
alert.addButton("About") {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("about") as UIViewController
self.presentViewController(vc, animated: true, completion: nil)
}
and so on....
And this works perfectly fine when I launch the app to the view controller containing these IBActions.
However, the problem is when I perform a modal/push transition into the said view controller, the IBAction's aren't being called....
Any ideas?
I just dealt with this issue too, may not be a general solution as for me it was a silly mistake, but the issue was that i had changed the class of the 'view' inside my ViewController:
The highlighted view was named something else until i changed it back to UIView in the identity inspector:
Hope that helps you, or someone else :)