I'm trying to share my app using UIActivityViewController but I can't reproduce the same effect as when I share an app from the App Store, meaning :
When I clicked the share button in the App Store I have something that looks like this :
But when I try to share my app I have this :
The code that I used was :
if let logo = UIImage(named: "myLogo"), let websiteURL = URL(string: "https://itunes.apple.com/app/idxxxxxxxxxx") {
let objectsToShare = ["My App Name", websiteURL, logo] as [Any]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: [])
if let popoverController = activityVC.popoverPresentationController {
popoverController.sourceView = self.view
popoverController.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
}
present(activityVC, animated: true)
}
The if let popoverController = ... loop is for preventing a crash when using iPads.
What do I have to change in order to have the effect as the App Store? (to have an image with a title and a subtitle)
Moreover, once I share the app with Messages for instance, this is the difference :
How can I have the same effect? (A single image with the title and subtitle, an as a bonus, a video). I'm not sure if this is an iOS 13 problem, since all similar questions don't have the same app sharing popover.
You have to use the new LinkPresentation framework.
Which essentially involves UIActivityItemSource conformance then retrieving the metadata that will encompass the Activity View and the data you are sharing. Data can be retrieved locally or downloaded.
ExampleController: UIViewController {
var metadata: LPLinkMetadata?
func share() {
let activityView = UIActivityViewController(activityItems: [self], applicationActivities: nil)
present(activityView, animated: true)
}
...
}
extension ExampleController: UIActivityItemSource {
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return metadata
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return metadata
}
func activityViewControllerLinkMetadata(_: UIActivityViewController) -> LPLinkMetadata? {
metadata = LPLinkMetadata()
metadata.title = "Title"
metadata.originalURL = URL(string: "Description")
metadata.url = metadata.originalURL
// Using a locally stored item
metadata.iconProvider = NSItemProvider(object: UIImage(named: "image")!)
metadata.imageProvider = NSItemProvider.init(contentsOf:
Bundle.main.url(forResource: "image", withExtension: "JPG"))
return metadata
}
}
Docs:
https://developer.apple.com/documentation/uikit/uiactivityitemsource/3144571-activityviewcontrollerlinkmetada
WWDC Presentation:
https://developer.apple.com/videos/play/wwdc2019/262/
activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash
You can use this.
Related
I'm having issues with dynamic links and social metadata. Seems to work fine when sharing on Facebook... image is attached properly. But when I share using Messages on iOS... no image appears. Just the Firebase folder logo.
I had the exact same issue and I found a way to show preview image in iMessage by creating LPLinkMetadata of LinkPresentation. It seems to be working even though it's not solving the core issue.
Basically, I downloaded the image to show in the preview first and create LPLinkMetadata based on that image.
let image = UIImage(data: data)! //Image to show in preview
let metadata = LPLinkMetadata()
metadata.imageProvider = NSItemProvider(object: image)
metadata.originalURL = url //dynamic links
metadata.title = "Holland Bloorview Kids Rehabilitation Hospital on Flixxaid"
let metadataItemSource = LinkPresentationItemSource(metaData: metadata)
let activity = UIActivityViewController(activityItems: [metadataItemSource], applicationActivities: [])
self.present(activity, animated: true)
And LinkPresentaionItemSource is from this blog.
class LinkPresentationItemSource: NSObject, UIActivityItemSource {
var linkMetaData = LPLinkMetadata()
//Prepare data to share
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
return linkMetaData
}
//Placeholder for real data, we don't care in this example so just return a simple string
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return "Placeholder"
}
/// Return the data will be shared
/// - Parameters:
/// - activityType: Ex: mail, message, airdrop, etc..
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return linkMetaData.originalURL
}
init(metaData: LPLinkMetadata) {
self.linkMetaData = metaData
}
}
I am bringing up a share sheet for sharing a link on iOS13. By default, it will fetch the title and image at the top of the sheet from the shared URL, and I would like to overwrite both. I have the title ready, but the image is remote and needs to be fetched. Preferrably, I would like the share sheet with the title to show up instantly, and the remote image being loaded in afterwards.
I found out about a new method in UIActivityItemSource that seems to do this, so I created a custom UIActivityItemSource subclass:
#objc class CustomURLItemSource: NSObject, UIActivityItemSource {
...
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return shareURL
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return shareURL
}
#available(iOS 13.0, *)
func activityViewControllerLinkMetadata(_: UIActivityViewController) -> LPLinkMetadata? {
let metadata = LPLinkMetadata()
metadata.originalURL = shareURL
metadata.url = shareURL
metadata.title = "My custom title"
metadata.imageProvider = NSItemProvider(contentsOf: imageURL)
return metadata
}
}
Using this item source, my custom title will show up, but the image next to the title will just be the default safari icon. How does iOS expect me to provide the image or image url here?
In my case, in activityViewControllerLinkMetadata I added icon image as:
let image = UIImage(named: "app_icon")!
let imageProvider = NSItemProvider(object: image)
let metadata = LPLinkMetadata()
metadata.imageProvider = imageProvider
and after I presented it as:
let url = URL(string: "your_url_string")
let shareSheetVC = UIActivityViewController(activityItems: [url, self], applicationActivities: nil)
present(shareSheetVC, animated: true, completion: nil)
The new share sheet on iOS13 shows a preview/thumbnail of the item being shared on its top left corner.
When sharing an UIImage using an UIActivityViewController I would expect a preview/thumbnail of the image being shared to be displayed there (like e.g. when sharing an image attached to the built in Mail app), but instead the share sheet is showing my app's icon.
What code/settings are required to show a thumbnail of the image being exported in the share sheet?
I have set up the UIActivityViewController as follows:
let image = UIImage(named: "test")!
let activityVC = UIActivityViewController(activityItems: [image], applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = self.view
self.present(activityVC, animated: true, completion: nil)
The simplest code I've implemented to share a UIImage with better user experience:
Import the LinkPresentation framework:
#import <LinkPresentation/LPLinkMetadata.h> // for Obj-C
import LinkPresentation // for Swift, below
Present the UIActivityViewController in the UIViewController, with [image, self]:
let image = UIImage(named: "YourImage")!
let share = UIActivityViewController(activityItems: [image, self], applicationActivities: nil)
present(share, animated: true, completion: nil)
Make the UIViewController conform to UIActivityItemSource:
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return ""
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return nil
}
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
let image = UIImage(named: "YourImage")!
let imageProvider = NSItemProvider(object: image)
let metadata = LPLinkMetadata()
metadata.imageProvider = imageProvider
return metadata
}
Because UIImage has already conformed to NSItemProviderWriting, just serve it for NSItemProvider.
Since it's sharing a UIImage, any URL shouldn't be expected. Otherwise user may get URL sharing, rather than image sharing experience.
To accelerate the share sheet preview, feed LPLinkMetadata object with existing resources. No need to fetch it online again. Check the WWDC19 Tech Talks video What's New in Sharing for more details.
Update:
As of iOS 13.2.2 the standard way seems to be working as expected (when passing image URL(s) to UIActivityViewController), see #tatsuki.dev 's answer (now set as accepted answer):
On iOS 13.0 that was still not the case:
Original Answer:
I finally was able to figure out a solution to this issue.
To display the preview/thumbnail of the image being shared in the share sheet on iOS 13 it is necessary to adopt the UIActivityItemSource protocol, including its new (iOS13) activityViewControllerLinkMetadata method.
Starting from the code posted in the question, these would be the required steps:
Import the LinkPresentation framework:
import LinkPresentation
create an optional URL property in your UIViewController subclass
var urlOfImageToShare: URL?
Implement the UIActivityItemSource delegate methods as follows:
extension YourViewController: UIActivityItemSource {
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return UIImage() // an empty UIImage is sufficient to ensure share sheet shows right actions
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return urlOfImageToShare
}
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
let metadata = LPLinkMetadata()
metadata.title = "Description of image to share" // Preview Title
metadata.originalURL = urlOfImageToShare // determines the Preview Subtitle
metadata.url = urlOfImageToShare
metadata.imageProvider = NSItemProvider.init(contentsOf: urlOfImageToShare)
metadata.iconProvider = NSItemProvider.init(contentsOf: urlOfImageToShare)
return metadata
}
}
In the part of the code presenting the share sheet, the declaration of activityVC needs to be slightly changed. The activityItems parameter should be [self] instead of [image] as in the code posted in the question above:
//let activityVC = UIActivityViewController(activityItems: [image], applicationActivities: nil)
let activityVC = UIActivityViewController(activityItems: [self] , applicationActivities: nil)
This is necessary to have the UIActivityItemSource delegate methods declared above being called when presenting the share sheet.
Also, before presenting activityVC we need to set the value of urlOfImageToShare (which is needed by the UIActivityItemSource delegate methods):
urlOfImageToShare = yourImageURL // <<< update this to work with your code
The above steps should suffice if your app is not sharing very small or transparent images. The result looks like this:
In my tests while researching about this topic however, I had issues when providing images to metadata.iconProvider which were small (threshold seems to be 40 points) or non-opaque (transparent).
It seems like iOS uses metadata.imageProvider to generate the preview image if metadata.iconProvider delivers an image smaller than 40 points.
Also, on an actual device (iPhone Xs Max running iOS 13.1.2), the image provided by metadata.iconProvider would be displayed in reduced size on the share sheet in case it was not opaque:
On Simulator (iOS 13.0) this was not the case.
To work around these limitations, I followed these additional steps to ensure the preview image is always opaque and at least 40 points in size:
In the implementation of activityViewControllerLinkMetadata above, change the assignment of metadata.iconProvider as follows:
//metadata.iconProvider = NSItemProvider.init(contentsOf: urlOfImageToShare)
metadata.iconProvider = NSItemProvider.init(contentsOf: urlInTemporaryDirForSharePreviewImage(urlOfImageToShare))
Method urlInTemporaryDirForSharePreviewImage returns an URL to an opaque and if necessary enlarged copy of the image being shared created in the temporary directory:
func urlInTemporaryDirForSharePreviewImage(_ url: URL?) -> URL? {
if let imageURL = url,
let data = try? Data(contentsOf: imageURL),
let image = UIImage(data: data) {
let applicationTemporaryDirectoryURL = FileManager.default.temporaryDirectory
let sharePreviewURL = applicationTemporaryDirectoryURL.appendingPathComponent("sharePreview.png")
let resizedOpaqueImage = image.adjustedForShareSheetPreviewIconProvider()
if let data = resizedOpaqueImage.pngData() {
do {
try data.write(to: sharePreviewURL)
return sharePreviewURL
} catch {
print ("Error: \(error.localizedDescription)")
}
}
}
return nil
}
The actual generation of the new image is done using the following extension:
extension UIImage {
func adjustedForShareSheetPreviewIconProvider() -> UIImage {
let replaceTransparencyWithColor = UIColor.black // change as required
let minimumSize: CGFloat = 40.0 // points
let format = UIGraphicsImageRendererFormat.init()
format.opaque = true
format.scale = self.scale
let imageWidth = self.size.width
let imageHeight = self.size.height
let imageSmallestDimension = max(imageWidth, imageHeight)
let deviceScale = UIScreen.main.scale
let resizeFactor = minimumSize * deviceScale / (imageSmallestDimension * self.scale)
let size = resizeFactor > 1.0
? CGSize(width: imageWidth * resizeFactor, height: imageHeight * resizeFactor)
: self.size
return UIGraphicsImageRenderer(size: size, format: format).image { context in
let size = context.format.bounds.size
replaceTransparencyWithColor.setFill()
context.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
self.draw(in: CGRect(origin: .zero, size: size))
}
}
}
Just pass the image urls to UIActivityViewController not the UIImage objects.
For example:
let imageURLs: [URL] = self.prepareImageURLs()
let activityViewController = UIActivityViewController(activityItems: imageURLs, applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)
You can see that the image name and the image properties are shown in the top of the UIActivityViewController. Hope it helps!
This code is only available for iOS 13 as a minimum target. I added a code example to use a share button in a SwiftUI view in case other people need it. This code also work for iPad.
You can use this class LinkMetadataManager and add the image of your choice. The very important part, is that you must have your image in your project directory, not in a Assets.xcassets folder. Otherwise, it won't work.
When everything will be setup, you will use the button this way in your SwiftUI view.
struct ContentView: View {
var body: some View {
VStack {
ShareButton()
}
}
}
This is the class that will be sharing your application with the Apple Store link. You can share whatever you want from that. You can see how the image is added using LPLinkMetadata as it is the part that interests you.
import LinkPresentation
// MARK: LinkMetadataManager
/// Transform url to metadata to populate to user.
final class LinkMetadataManager: NSObject, UIActivityItemSource {
var linkMetadata: LPLinkMetadata
let appTitle = "Your application name"
let appleStoreProductURL = "https://apps.apple.com/us/app/num8r/id1497392799" // The url of your app in Apple Store
let iconImage = "appIcon" // The name of the image file in your directory
let png = "png" // The extension of the image
init(linkMetadata: LPLinkMetadata = LPLinkMetadata()) {
self.linkMetadata = linkMetadata
}
}
// MARK: - Setup
extension LinkMetadataManager {
/// Creating metadata to population in the share sheet.
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
guard let url = URL(string: appleStoreProductUR) else { return linkMetadata }
linkMetadata.originalURL = url
linkMetadata.url = linkMetadata.originalURL
linkMetadata.title = appTitle
linkMetadata.iconProvider = NSItemProvider(
contentsOf: Bundle.main.url(forResource: iconImage, withExtension: png))
return linkMetadata
}
/// Showing empty string returns a share sheet with the minimum requirement.
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return String()
}
/// Sharing url of the application.
func activityViewController(_ activityViewController: UIActivityViewController,
itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return linkMetadata.url
}
}
Use this extension of View to trigger the share sheet on a SwiftUI view.
import SwiftUI
// MARK: View+ShareSheet
extension View {
/// Populate Apple share sheet to enable user to share Apple Store link.
func showAppShareSheet() {
guard let source = UIApplication.shared.windows.first?.rootViewController else {
return
}
let activityItemMetadata = LinkMetadataManager()
let activityVC = UIActivityViewController(
activityItems: [activityItemMetadata],
applicationActivities: nil)
if let popoverController = activityVC.popoverPresentationController {
popoverController.sourceView = source.view
popoverController.permittedArrowDirections = []
popoverController.sourceRect = CGRect(
x: source.view.bounds.midX,
y: source.view.bounds.midY,
width: .zero,
height: .zero)
}
source.present(activityVC, animated: true)
}
}
Then, create a ShareButton as a component to use it in any of your SwiftUI view. This is what is used in the ContentView.
import SwiftUI
// MARK: ShareButton
/// Share button to send app store link using the Apple
/// classic share screen for iPhone and iPad.
struct ShareButton: View {
#Environment(\.horizontalSizeClass) private var horizontalSizeClass
var body: some View {
ZStack {
Button(action: { showAppShareSheet() }) {
Image(systemName: "square.and.arrow.up")
.font(horizontalSizeClass == .compact ? .title2 : .title)
.foregroundColor(.accentColor)
}
.padding()
}
}
}
I Created and extension for sharing text along with Image.
Here is the extension class
extension UIViewController {
func shareScreen(shareText:String?,shareImage:UIImage?){
var objectsToShare = [AnyObject]()
if let shareTextObj = shareText{
objectsToShare.append(shareTextObj as AnyObject)
}
if let shareImageObj = shareImage{
objectsToShare.append(shareImageObj)
}
if shareText != nil || shareImage != nil{
let activityViewController = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
present(activityViewController, animated: true, completion: nil)
}else{
print("There is nothing to share")
}
}
}
share if its contains images only.
guard loadImage == nil else {
self.shareScreen(shareText: "AppName", shareImage: loadImage)
return
}
For Other share accessories link (Message,Email,Skype,Facebook) it worked.
When Try to share via (LinkedIn & WhatsApp) the image is not showing?
The above code which I tried, Can any one let me know is there any provision to share text along with Image including with linkedIn & WhatsApp.
My app is not Live yet. I got the app ID from the App Store Connect. I want to share the app link on social media apps. I used the UIActivityViewController:
let string1 = "itms-apps://itunes.apple.com/app/idXXXXXXX"
let url = NSURL(string: string1)
let shareItems = [UIApplication.sharedApplication().openURL(url!)]
let activityViewController = UIActivityViewController(activityItems: shareItems, applicationActivities: nil)
self.presentViewController(activityViewController, animated: true, completion: nil)
Problem: It is not showing some social media apps like WhatsApp.
Solution for Swift 4 or better:
This solution also works on iPad (the solution above crashes on iPad):
if let urlStr = NSURL(string: "https://apps.apple.com/us/app/idxxxxxxxx?ls=1&mt=8") {
let objectsToShare = [urlStr]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
if UIDevice.current.userInterfaceIdiom == .pad {
if let popup = activityVC.popoverPresentationController {
popup.sourceView = self.view
popup.sourceRect = CGRect(x: self.view.frame.size.width / 2, y: self.view.frame.size.height / 4, width: 0, height: 0)
}
}
self.present(activityVC, animated: true, completion: nil)
}
This is used to open the site, not to share the app:
[UIApplication.sharedApplication().openURL(url!)]
Do this instead:
if let name = URL(string: "https://itunes.apple.com/us/app/myapp/idxxxxxxxx?ls=1&mt=8"), !name.absoluteString.isEmpty {
let objectsToShare = [name]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
self.present(activityVC, animated: true, completion: nil)
} else {
// show alert for not available
}
for sample see this
The solutions here are all good but it's worth considering implementing the UIActivityItemSource protocol and LinkPresentation framework.
My solution achieves the following:
App icon and title showing at the top of the UIActivityViewController
Direct link to App Store for AirDrop ActivityType
Custom text for messages and emails, including an opportunity to add a link to the app on Google Play if required
Subject for emails
Doesn't use the LPMetaDataProvider fetch request (as described in this WWDC 2019 262 video) so faster to load
0. Init the UIActivityViewController:
Set the items to self:
let activityVC = UIActivityViewController(activityItems: [self], applicationActivities: nil)
Exclude certain ActivityTypes which don't apply:
activityVC.excludedActivityTypes = [.addToReadingList, .assignToContact, .markupAsPDF, .openInIBooks, .saveToCameraRoll]
For iPad set the popoverPresentationController.sourceView or .barButtonItem (this is ignored on iPhone):
activityVC.popoverPresentationController?.sourceView = myButton
Present it:
present(activityVC, animated: true, completion: nil)
1. Implement the required UIActivityItemSource methods
https://developer.apple.com/documentation/uikit/uiactivityitemsource
You must implement the placeholder method which according to the docs:
Placeholder objects do not have to contain any real data but should be configured as closely as possible to the actual data object you intend to provide.
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return ""
}
And the actual data, returning a link to the app for AirDrop and text for everything else:
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
if activityType == .airDrop {
return URL(string: "APP_STORE_URL")!
}
return "Check out the APP_NAME on the App Store: APP_STORE_URL or on the Google Play Store: PLAY_STORE_URL"
}
2. Implement the subject method
From the docs:
For activities that support a subject field, returns the subject for the item.
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
return "EMAIL_SUBJECT" // e.g. App name
}
3. Implement the LPLinkMetaData method
From the docs:
Returns metadata to display in the preview header of the share sheet.
#available(iOS 13.0, *)
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
let metadata = LPLinkMetadata()
metadata.title = "APP_NAME"
return metadata
}
iPhone solution for Swift 5+
let url = URL(string: "https://apps.apple.com/us/app/id1535629801")!
let vc = UIActivityViewController(activityItems: [url], applicationActivities: nil)
present(vc, animated: true)