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

Posted

技术标签:

【中文标题】iOS13分享表:分享UIImage时如何设置预览缩略图【英文标题】:iOS13 share sheet: how to set preview thumbnail when sharing UIImage 【发布时间】:2019-09-09 08:39:39 【问题描述】:

ios13 上的新共享表在其左上角显示正在共享的项目的预览/缩略图。

当使用 UIActivityViewController 共享 UIImage 时,我希望在那里显示正在共享的图像的预览/缩略图(例如,当共享附加到内置邮件应用程序的图像时),但共享表显示的是我的应用程序的图标。

需要哪些代码/设置才能在共享表中显示正在导出的图像的缩略图?

我已将 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)

【问题讨论】:

你有什么发现吗?我遇到了同样的问题,这很奇怪:如果我在 Documents 目录中使用 URL 的 .jpg 图像而不是 UIImage,它会显示有关该图像的信息(名称和大小) ,但仍然没有缩略图。但是,如果我在应用程序包内使用图像的URL,它会显示该图像的缩略图!很奇怪。 不幸的是,@gilby 有没有人设法为网站做到这一点?我面临着类似的情况,我试图在使用本机共享或( navigator.share )时显示徽标。没有明确的文件是从哪里获取图像的。任何有建议的人请发表! @SijanShrestha 你找到解决这个问题的方法了吗?我也在尝试使用navigator.share 获取徽标,但我似乎无法获得它。 @mayk93,是的,我有解决方案。如果你想要这个图标,你应该只传递 URL。不要传递文本。希望能帮助到你。如果您传递 URL,来自 Apple 的机器人将抓取该站点并获取图标。 【参考方案1】:

我实现的最简单的代码共享UIImage 具有更好的用户体验:

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

import LinkPresentation  // for Swift, below
    UIViewController中展示UIActivityViewController,用[image, self]
let image = UIImage(named: "YourImage")!
let share = UIActivityViewController(activityItems: [image, self], applicationActivities: nil)
present(share, animated: true, completion: nil)
    使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

因为UIImage已经符合NSItemProviderWriting,就为NSItemProvider服务吧。

由于它共享 UIImage,因此不应期望任何 URL。否则用户可能会获得 URL 共享,而不是图像共享体验。

要加速共享表预览,请向LPLinkMetadata 对象提供现有资源。无需再次在线获取。查看 WWDC19 技术讲座视频What's New in Sharing 了解更多详情。

【讨论】:

非常感谢您的回答!我仍然缺少一件事,我也可以通过元数据设置文件名吗?我用这个来分享一个pdf文件。 @laka 我相信你可以通过正确使用 UIActivityViewControllerURL 来做到这一点 我看到我的图像周围有一个填充。使用了所有可能大小的图像并进行了检查。知道可能是什么问题吗?【参考方案2】:

更新:

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

在 iOS 13.0 上仍然不是这样:

原答案:

我终于找到了解决这个问题的办法。

要在 iOS 13 的共享表中显示正在共享的图像的预览/缩略图,需要采用 UIActivityItemSource 协议,包括其新的 (iOS13) activityViewControllerLinkMetadata 方法。

从问题中发布的代码开始,这些是必需的步骤:

    导入 LinkPresentation 框架:

    import LinkPresentation
    

    在你的 UIViewController 子类中创建一个可选的 URL 属性

    var urlOfImageToShare: URL?
    

    实现 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
        
    
    

    在呈现share sheet的代码部分,activityVC的声明需要稍作改动。 activityItems 参数应该是 [self] 而不是 [image],如上面问题中发布的代码:

    //let activityVC = UIActivityViewController(activityItems: [image], applicationActivities: nil)   
    let activityVC = UIActivityViewController(activityItems: [self] , applicationActivities: nil)
    

    这是在呈现共享表时调用上面声明的 UIActivityItemSource 委托方法所必需的。

    另外,在展示 activityVC 之前,我们需要设置 urlOfImageToShare 的值(这是 UIActivityItemSource 委托方法所需要的):

    urlOfImageToShare = yourImageURL // <<< update this to work with your code 
    

如果您的应用不共享非常小或透明的图像,上述步骤就足够了。结果如下所示:

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

如果 metadata.iconProvider 提供的图像小于 40 点,iOS 似乎使用 metadata.imageProvider 生成预览图像。

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

在模拟器 (iOS 13.0) 上并非如此。

为了解决这些限制,我遵循了这些额外的步骤来确保预览图像始终不透明且大小至少为 40 点:

    在上面activityViewControllerLinkMetadata的实现中,改变metadata.iconProvider的赋值如下:

    //metadata.iconProvider = NSItemProvider.init(contentsOf: urlOfImageToShare)
    metadata.iconProvider = NSItemProvider.init(contentsOf: urlInTemporaryDirForSharePreviewImage(urlOfImageToShare))
    

    方法 urlInTemporaryDirForSharePreviewImage 返回一个不透明的 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
    
    

    新图像的实际生成是使用以下扩展完成的:

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

【讨论】:

您通过解决填充问题节省了一天的时间。我正想弄清楚为什么我的图标会有一个填充。 "padding" = 图标周围的空白,因为它被赋予了部分透明的图像。解决方法:让图片背景不透明!【参考方案3】:

只需将图像 URL 传递给 UIActivityViewController 而不是 UIImage 对象。 例如:

let imageURLs: [URL] = self.prepareImageURLs()
let activityViewController = UIActivityViewController(activityItems: imageURLs, applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)

您可以看到图像名称和图像属性显示在UIActivityViewController 的顶部。希望对您有所帮助!

【讨论】:

感谢 tatsuki.dev 的更新。正如上面@gilby 的评论中提到的,这在 iOS 13.0 下无法正常工作,并且从那时起似乎已经修复。我已将您的答案设为可接受的答案。 如何从 UIimage 获取 url? @NicWanavit 据我了解,你不能。 UIImage 代表图像数据本身并且没有它的位置。当您将图像保存在设备中或从网络加载图像时,它带有一个 uri。如需使用资产目录中的图片,请参考forums.developer.apple.com/thread/93123 嘿@tatsuki.dev,prepareImageURLs() 是一种可以返回数组中图像的URL 的方法吗?这究竟是如何工作的?我一直在尝试使用 Bundle.main.url(forResource:, withExtension) 来获取我正在共享的图像的路径,但它总是为零。【参考方案4】:

此代码仅适用于 iOS 13 作为最低目标。我添加了一个代码示例以在 SwiftUI 视图中使用共享按钮,以防其他人需要它。此代码也适用于 iPad。

您可以使用此类LinkMetadataManager 并添加您选择的图像。非常重要的部分是您必须将图像放在项目目录中,而不是在 Assets.xcassets 文件夹中。否则,它将不起作用。

一切就绪后,您将在 SwiftUI 视图中以这种方式使用按钮。

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

该类将与 Apple Store 链接共享您的应用程序。你可以分享任何你想要的东西。您可以使用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
  

使用 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)
  

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

【讨论】:

以上是关于iOS13分享表:分享UIImage时如何设置预览缩略图的主要内容,如果未能解决你的问题,请参考以下文章

如何设置将在 Facebook 上显示为预览的网站图像?

使用较小的 UIImage 为 UIActivityViewController 设置缩略图

iOS小技能:文件预览分享

如何在ios中从UIImage设置图像

IOS 分享到 Facebook 的 URL 不显示预览

iOS13之前如何访问UIImage深色外观