UIActivityViewControllerのHeaderをカスタマイズする

はじめに

UIActivityViewControllerのHeaderのサブタイトル部分に独自の説明文を入れたいと考えているそこのあなた!現在(iOS15では)実現不可能です😭

サブタイトルに何かしらの文字が入ってるありますが、あればURLを渡した時のメタデータが入ります。

じゃーどこまでカスタマイズできるかというと独自のアイコン画像とタイトルの文字を入れることだけです。

文字と画像どうやって入れるのか

標準で渡せる形でTextとUIImageを渡すとどうなるかというとこれでは機能しません。

let items: [Any] = ["リンクをコピーしました", UIImage(named: "sample")]

let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = self.view
activityVC.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.size.width,
                                                              y: self.view.bounds.size.height / 2.0,
                                                              width: 1.0,
                                                              height: 1.0)
self.present(activityVC, animated: true, completion: nil)

画像のように表示されません、これはString > URL > Data(UIImage等)という関係せいがあるからだそうです。

ただこちらも公式ドキュメントの引用ではなく記事を書いた方の検証結果とのことなのでOSが変わった時にAppleによって勝手に変えられる可能性も考慮すべきだと思います。

タイトルとアイコン両方出す

両方出すにはiOS13からUIActivityItemSourceに追加された以下のメソッドの中でアイコンに使用する画像とタイトルを設定する必要があります。

func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata?

結果

コード

CustomShareItem

UIActivityItemSourceに準拠したクラスを作成してLPLinkMetadataを返します。このメソッド自体iOS13からできたもので13以下で実現できるのかは不明

import LinkPresentation

class CustomShareItem: NSObject, UIActivityItemSource {
    
    private let metadata: LPLinkMetadata
    
    init(metadata: LPLinkMetadata) {
        self.metadata = metadata
    }
    
    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        return ""
    }
    
    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        return activityViewControllerPlaceholderItem(activityViewController)
    }
    
    func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
        return metadata
    }
}

AssetExtractor

URLで画像を渡す必要があるのでAssetsにある画像のURLを取得する必要があるんですが便利なメソッドが転がってなので使わせていただきました。

感謝🙏

class AssetExtractor {
    static func createLocalUrl(forImageNamed name: String) -> URL? {

        let fileManager = FileManager.default
        let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0]
        let url = cacheDirectory.appendingPathComponent("\(name).png")

        guard fileManager.fileExists(atPath: url.path) else {
            guard
                let image = UIImage(named: name),
                let data = image.pngData()
            else { return nil }

            fileManager.createFile(atPath: url.path, contents: data, attributes: nil)
            return url
        }

        return url
    }
}

呼び出しもと

class ViewController: UIViewController {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        
        let metadata = LPLinkMetadata()
        metadata.title = "タイトルタイトルタイトル"
        metadata.iconProvider = NSItemProvider(contentsOf: AssetExtractor.createLocalUrl(forImageNamed: "sample"))
        let activityVC = UIActivityViewController(activityItems: [CustomShareItem(metadata: metadata)], applicationActivities: nil)
        present(activityVC, animated: true, completion: nil)
    }
}

参考文献

Embedding and Sharing Visually Rich Links

fahied/AssetExtractor.swift

[iOS] シェアシート上のプレビューをあるべき姿に

Xcod11&iOS13のUIActivityViewControllerのプレビュー画面の挙動が実装方法によって異なる