【问题标题】:iOS13 share sheet: how to set preview thumbnail when sharing UIImageiOS13分享表:分享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 的机器人将抓取该站点并获取图标。

标签: ios uiactivityviewcontroller ios13


【解决方案1】:

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

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

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

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

  1. 导入 LinkPresentation 框架:

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

    var urlOfImageToShare: URL?
    
  3. 实现 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. 在呈现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 点:

  1. 在上面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()
    }
  }
}

【讨论】:

    猜你喜欢
    • 2018-01-03
    • 1970-01-01
    • 2019-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-28
    • 2011-12-26
    • 2012-11-10
    相关资源
    最近更新 更多