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,
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.
Only if I wait for the thumbnail to be generated, and then hit send, everything will then work.
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)
}
}
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)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With