简体   繁体   English

iOS13分享表:分享UIImage时如何设置预览缩略图

[英]iOS13 share sheet: how to set preview thumbnail when sharing UIImage

The new share sheet on iOS13 shows a preview/thumbnail of the item being shared on its top left corner. iOS13 上的新共享表在其左上角显示了共享项目的预览/缩略图。

When sharing an UIImage using an UIActivityViewController I would expect a preview/thumbnail of the image being shared to be displayed there (like eg when sharing an image attached to the built in Mail app), but instead the share sheet is showing my app's icon.当使用 UIActivityViewController 共享 UIImage 时,我希望共享图像的预览/缩略图显示在那里(例如共享附加到内置邮件应用程序的图像时),但共享表显示的是我的应用程序图标。

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:我已经设置了 UIActivityViewController 如下:

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:我实现的最简单的代码用于共享具有更好用户体验的UIImage

  1. Import the LinkPresentation framework:导入 LinkPresentation 框架:
#import <LinkPresentation/LPLinkMetadata.h>  // for Obj-C

import LinkPresentation  // for Swift, below
  1. Present the UIActivityViewController in the UIViewController , with [image, self] :UIViewController显示 UIActivityViewController ,使用[image, self]
let image = UIImage(named: "YourImage")!
let share = UIActivityViewController(activityItems: [image, self], applicationActivities: nil)
present(share, animated: true, completion: nil)
  1. Make the UIViewController conform to UIActivityItemSource :使UIViewController符合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 .因为UIImage已经符合NSItemProviderWriting ,所以只为NSItemProvider

Since it's sharing a UIImage , any URL shouldn't be expected.由于它共享UIImage ,因此不应期望任何 URL。 Otherwise user may get URL sharing, rather than image sharing experience.否则用户可能会获得 URL 共享,而不是图像共享体验。

To accelerate the share sheet preview, feed LPLinkMetadata object with existing resources.要加速共享表预览, LPLinkMetadata使用现有资源提供LPLinkMetadata对象。 No need to fetch it online again.无需再次在线获取。 Check the WWDC19 Tech Talks video What's New in Sharing for more details.查看 WWDC19 技术讲座视频共享中的新功能了解更多详细信息。

Just pass the image urls to UIActivityViewController not the UIImage objects.只需将图像 url 传递给UIActivityViewController而不是UIImage对象。 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 .您可以看到图像名称和图像属性显示在UIActivityViewController的顶部。 Hope it helps!希望能帮助到你!

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):从 iOS 13.2.2 开始,标准方式似乎按预期工作(将图像 URL 传递给 UIActivityViewController 时),请参阅 @tatsuki.dev 的答案(现在设置为接受的答案):

On iOS 13.0 that was still not the case:在 iOS 13.0 上,情况仍然不是这样:

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.要在 iOS 13 上的共享表中显示共享图像的预览/缩略图,必须采用 UIActivityItemSource 协议,包括其新的 (iOS13) activityViewControllerLinkMetadata 方法。

Starting from the code posted in the question, these would be the required steps:从问题中发布的代码开始,这些将是必需的步骤:

  1. Import the LinkPresentation framework:导入 LinkPresentation 框架:

     import LinkPresentation
  2. create an optional URL property in your UIViewController subclass在 UIViewController 子类中创建一个可选的 URL 属性

    var urlOfImageToShare: URL?
  3. Implement the UIActivityItemSource delegate methods as follows:实现 UIActivityItemSource 委托方法如下:

     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 } }
  4. In the part of the code presenting the share sheet, the declaration of activityVC needs to be slightly changed.在呈现共享表的代码部分,activityVC 的声明需要稍作改动。 The activityItems parameter should be [self] instead of [image] as in the code posted in the question above: activityItems 参数应为 [self] 而不是 [image],如上述问题中发布的代码所示:

     //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.这是在呈现共享表时调用上面声明的 UIActivityItemSource 委托方法所必需的。

    Also, before presenting activityVC we need to set the value of urlOfImageToShare (which is needed by the UIActivityItemSource delegate methods):此外,在呈现 activityVC 之前,我们需要设置 urlOfImageToShare 的值(UIActivityItemSource 委托方法需要此值):

     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).然而,在我研究这个主题的测试中,我在向 metadata.iconProvider 提供很小(阈值似乎是 40 点)或不透明(透明)的图像时遇到了问题。

It seems like iOS uses metadata.imageProvider to generate the preview image if metadata.iconProvider delivers an image smaller than 40 points.如果 metadata.iconProvider 提供的图像小于 40 点,则似乎 iOS 使用 metadata.imageProvider 来生成预览图像。

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:此外,在实际设备(运行 iOS 13.1.2 的 iPhone Xs Max)上,metadata.iconProvider 提供的图像将在共享表上以缩小的尺寸显示,以防它不透明:

On Simulator (iOS 13.0) this was not the case.在模拟器 (iOS 13.0) 上,情况并非如此。

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:为了解决这些限制,我遵循了以下附加步骤以确保预览图像始终不透明且大小至少为 40 磅:

  1. In the implementation of activityViewControllerLinkMetadata above, change the assignment of metadata.iconProvider as follows:在上面activityViewControllerLinkMetadata的实现中,改变metadata.iconProvider的赋值如下:

     //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:方法 urlInTemporaryDirForSharePreviewImage 返回一个 URL,该 URL 指向在临时目录中创建的共享图像的不透明和必要的放大副本:

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

This code is only available for iOS 13 as a minimum target.此代码仅适用于 iOS 13 作为最低目标。 I added a code example to use a share button in a SwiftUI view in case other people need it.我添加了一个代码示例,以便在 SwiftUI 视图中使用共享按钮,以防其他人需要它。 This code also work for iPad.此代码也适用于 iPad。

You can use this class LinkMetadataManager and add the image of your choice.您可以使用此类LinkMetadataManager并添加您选择的图像。 The very important part, is that you must have your image in your project directory , not in a Assets.xcassets folder.非常重要的部分是,您必须将图像放在项目目录中,而不是在 Assets.xcassets 文件夹中。 Otherwise, it won't work.否则,它不会工作。

When everything will be setup, you will use the button this way in your SwiftUI view.当一切都设置好后,您将在 SwiftUI 视图中以这种方式使用该按钮。

struct ContentView: View {
    
  var body: some View {
    VStack {
      ShareButton()
    }
  }
}

This is the class that will be sharing your application with the Apple Store link.这是将与 Apple Store 链接共享您的应用程序的类。 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.您可以查看如何使用LPLinkMetadata添加图像,因为它是您感兴趣的部分。

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.使用此View扩展在 SwiftUI 视图上触发共享表。

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.然后,创建一个ShareButton作为组件以在您的任何 SwiftUI 视图中使用它。 This is what is used in the ContentView.这就是 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()
    }
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM