Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set thumbnail image for UIActivityViewController with smaller UIImage

I'd like to set a specific smaller thumbnail, but still use a high quality image for the activity item I'm sharing. My problem is when sharing large images, the thumbnail takes time to generate, which can cause problems when sharing it to certain apps.

For instance, if I present my UIActivity controller, and choose Messenger,

enter image description here

and I don't give time to let my thumbnail load, choose a person and hit send, it will never finish sending, and I need to cancel.

enter image description here

Only if I wait for the thumbnail to be generated, and then hit send, everything will then work.

enter image description here

In this case, "photo" is the high quality UIImage, but how do I set the thumbnail image to avoid it generating one out of the high quality image when the UIActivityViewController appears?

let activity = UIActivityViewController(activityItems: [photo], applicationActivities: nil)

UIApplication.topViewController?.present(activity, animated: true, completion: nil)

There's a document about thumbnail images, but I'm not sure how implement it, or if it's helpful in this situation: https://developer.apple.com/documentation/uikit/uiactivityitemsource/1620462-activityviewcontroller

Also, I read up on UIActivityItemProvider, and it states:

For example, you might use a provider object to represent a large video file that needs to be processed before it can be shared to a user’s social media account.

Does this mean it can hold off on displaying the app I want to share my image with until it's ready? I subclassed UIActivityItemProvider, and pass a thumbnail image, but it doesn't seem to have any effect. I'm either doing something completely wrong, or this isn't what I want to use.

My subclassed UIActivityItemProvider:

class CustomProvider : UIActivityItemProvider {

    var image : UIImage!

    init(placeholderItem: AnyObject, image : UIImage) {
        super.init(placeholderItem: placeholderItem)
        self.image = image
    }

    override var item: Any {
        return self.image!
    }
}

I have a share function that is an extension. I create a thumbnail image and pass it to the placeholderItem in the CustomProvider function:

extension Equatable {
    func share() {

        let imageData = (self as! UIImage).pngData()!
        let options = [
            kCGImageSourceCreateThumbnailWithTransform: true,
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceThumbnailMaxPixelSize: 300] as CFDictionary
        let source = CGImageSourceCreateWithData(imageData as CFData, nil)!
        let imageReference = CGImageSourceCreateThumbnailAtIndex(source, 0, options)!
        let thumbnail = UIImage(cgImage: imageReference)


        let firstActivityItem = CustomProvider(placeholderItem: thumbnail, image: self as! UIImage)

        let activity = UIActivityViewController(activityItems: [firstActivityItem], applicationActivities: nil)

        UIApplication.topViewController?.present(activity, animated: true, completion: nil)
    }
}
like image 369
Chewie The Chorkie Avatar asked Nov 07 '22 14:11

Chewie The Chorkie


1 Answers

Let the ActivityViewController do the work for you.

func activityViewController(activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: String?, suggestedSize size: CGSize) -> UIImage? {
    let maxSize = [size.width, size.height].max()
    let imageData = (self as! UIImage).pngData()!
    let options = [
            kCGImageSourceCreateThumbnailWithTransform: true,
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceThumbnailMaxPixelSize: maxSize as Any] as CFDictionary
    let source = CGImageSourceCreateWithData(imageData as CFData, nil)!
    let imageReference = CGImageSourceCreateThumbnailAtIndex(source, 0, options)!
    let thumbnail = UIImage(cgImage: imageReference)
    return thumbnail
}
let activity = UIActivityViewController(activityItems: [photo], applicationActivities: nil)

UIApplication.topViewController?.present(activity, animated: true, completion: nil)

I didn't think you needed a specific ActivityType so this assumes all activities.

Also, make sure you extend UIActivityItemSource.

I used this SO Post as a reference.

Edit1

Subclass UIActivityItemSource

class MyActivityType:NSObject, UIActivityItemSource {

    var image:UIImage!
    var placeholderImage:UIImage!

    convenience init(image:UIImage, placeholderImage:UIImage) {
        self.init()
        self.image = image
        self.placeholderImage = placeholderImage
    }

    //Placeholder - we use `return UIImage()` so the sheet knows it is an image
    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        return self.placeholderImage
    }

    //Actual item you are sending
    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        return self.image
    }

    //Thumbnail - Apple states that `For activities that support a preview image, returns a thumbnail preview image for the item.` but since you are pushing an image for your item, should be good to go.
    func activityViewController(_ activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: UIActivity.ActivityType?, suggestedSize size: CGSize) -> UIImage? {
        return self.placeholderImage
    }
}

I don't get any errors on this - but I can't run it, so I don't see any runtime errors.

You can then send as such --

DispatchQueue.global(qos: .userInitiated).async { [weak self] in

    let imageData = (self! as! UIImage).pngData()!
    let options = [
        kCGImageSourceCreateThumbnailWithTransform: true,
        kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceThumbnailMaxPixelSize: 300] as CFDictionary
    let source = CGImageSourceCreateWithData(imageData as CFData, nil)!
    let imageReference = CGImageSourceCreateThumbnailAtIndex(source, 0, options)!
    let thumbnail = UIImage(cgImage: imageReference)

    //Back to main thread
    DispatchQueue.main.async { [weak self] in

        //Remove loading indicator
        let firstActivityItem = MyActivityType(image: (self! as! UIImage), placeholderImage: thumbnail)

        let activity = UIActivityViewController(activityItems: [firstActivityItem], applicationActivities: nil)

        UIApplication.topViewController?.present(activity, animated: true, completion: nil)
    }
}
like image 53
impression7vx Avatar answered Nov 18 '22 11:11

impression7vx