iosswiftmacosuiactivityviewcontrollermac-catalyst

UIActivityViewController completionWithItemsHandler function not called in MacOS app


Background

The function shareImage() in the code below launches the UIActivityViewController and shares an image, for example sends to Messages or saves to Photos.

After the image is shared, UIActivityViewController finishes up, and calls the completionWithItemsHandler function activityComplete().

The code works fine and as expected on iOS / iPadOS / MacOS and shares the image, EXCEPT that on MacOS, the completionWithItemsHandler function activityComplete() never gets called, confirmed using the flag print("Share complete.").

Xcode instead logs the following output: Scene destruction request failed with error: (null)


Question

What could be causing the issue preventing the completionWithItemsHandler function activityComplete() from being called on MacOS, and how can this be corrected in code?


Code

Note: Updated with two lines added based on the answer.

import UIKit

class ViewController: UIViewController {

    var shareButton = UIButton(type: UIButton.ButtonType.custom)
    var activityImage: UIImage!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Create share button
        shareButton.setTitle("Share", for:UIControl.State())
        shareButton.setTitleColor(.white, for: UIControl.State())
        shareButton.backgroundColor = .purple
        shareButton.frame = CGRect(x: 50, y: 100, width: 200, height: 200)
        shareButton.addTarget(self, action: #selector(ViewController.shareImage), for:.touchUpInside)
        view.addSubview(shareButton)
    }


    private var activity: UIActivityViewController?  // <-- Added
    
    @objc func shareImage() {
        
        // Take view screenshot
        UIGraphicsBeginImageContextWithOptions(self.view.frame.size, false, 0)
        self.view.drawHierarchy(in: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height), afterScreenUpdates: false)
        activityImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        // Share view screenshot
        let activity = UIActivityViewController(activityItems: [activityImage as Any], applicationActivities: nil)
        
        // Present iPhone share options
        if UIDevice.current.userInterfaceIdiom == .phone {
            self.present(activity, animated: true, completion: nil)
        }
        
        // Present iPad share options
        if UIDevice.current.userInterfaceIdiom == .pad {
            self.present(activity, animated: true, completion: nil)
            if let popOver = activity.popoverPresentationController {
                popOver.sourceView = self.shareButton
            }
        }
        
        // Complete share
        activity.completionWithItemsHandler = {activity, success, items, error in
            self.activityComplete()
        }

        self.activity = activity  // <-- Added

    }
    
    func activityComplete() {
        
        print("Share complete.")
    }
}

Solution

  • Try to retain your activity view controller after presenting it. Something like:

        private var activityViewController: UIActivityViewController?
    
        @objc func presentActivityViewController(activityItems: [Any]) {
            let activityViewController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
            self.present(activityViewController, animated: true, completion: nil)
            activityViewController.completionWithItemsHandler = { activity, success, items, error in
                // some logic
            }
            self.activityViewController = activityViewController // <- retaining activityViewController
        }