5
votes

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)
    }
}
2
Why not create a smaller UIImage image initially, while waiting for the other image to be created? Of course, if the user hits send, you will have to wait for the larger image to be created anyway. So, in any situation, you are still waiting for the image to be created. The question is - are you going to allow users to press "I want to send something" and show a smaller image initially, or wait till everything is ready to load? Either way, you are waiting for the image to be ready.impression7vx
Yeah I don't mind having them wait. I just want something to appear there for the thumbnail, but ultimately send the larger image. But how do I do that?Chewie The Chorkie
Well, it depends; are you creating an image from within your app or obtaining it from the interwebs? If you are creating an image from within your app using an AVAssetWriter or something, then create 2 images. The first MUCH smaller (just a thumbnail size) and the other be the original. Use the smaller for your thumbnail and the original goes somewhere. If from the webs, then (if you are storing your own imagery) store 2 images, one of much smaller size.impression7vx
My question, however, is that users have to wait to send the "original" image anywho, so why not just make them wait to send the image until the original is created?impression7vx
The images are being made within my app, not the internet. The thumbnail seems to be created as the activity is selected, like Facebook Messenger, so I have no control over doing it beforehand. See code above. I can give it an array of activity items. If an image in that array signifies a thumbnail, I don't know which one it is. It seems to use whatever image is provided first in that array.Chewie The Chorkie

2 Answers

2
votes

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)
    }
}
-1
votes

You could generate a low quality image in your application and reference that url in the UIActivityViewController

let activity = UIActivityViewController(
activityItems: ["Some description", url],
applicationActivities: nil)

You may use the following to generate a low quality image which can be locally saved later:

NSData * UIImageJPEGRepresentation(UIImage *image, CGFloat compressionQuality);

... or refer the following post for more technical knowledge and ways to do it: http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/

Credits. Material extracted from the following locations: