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 我相信你可以通过正确使用UIActivityViewController
和 URL
来做到这一点
我看到我的图像周围有一个填充。使用了所有可能大小的图像并进行了检查。知道可能是什么问题吗?【参考方案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时如何设置预览缩略图的主要内容,如果未能解决你的问题,请参考以下文章