共有シートのプレビューの優先度と選択可能アプリについて~iOS~

公開:2020/12/12
更新:2020/12/12
7 min読了の目安(約6700字TECH技術記事

概要

共有シート(Share Sheet)を実装する機会があり、その際にプレビューの挙動と選択可能なアプリについて不明な点があったため、それらについてまとめてみました。

主要クラスについて

標準的な共有シートを実装するためには、主に以下のクラスを使うことになると思います。

  • UIActivityViewController
    標準的なサービス(e.g. ペーストボードへのデータのコピー, SNSへのデータの投稿, メールでのデータの送信 ext.)を提供するためのUIコンポーネントです。
  • UIActivityItemSource
    ユーザーに提供したいデータを表すためのプロトコルです。共有シートのプレビュー部分にもこのデータが利用されます。
  • UIActivity
    ユーザーに独自のサービスを提供するためのクラスです。コピーやSNSへの投稿など以外の独自の処理を行いたい場合に使用します。(本記事ではこのクラスは扱いません。)

プレビューについて

iOS13から共有シートにプレビューが表示されるようになりました。
ここではプレビューの挙動について見ていきます

データを1つだけ共有した場合

テキストを共有する

まずはテキストを共有してみます。

import UIKit

final class ViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
	showShareSheet()
    }
    
    private func showShareSheet() {
        let shareText = "Apple - Test"
	let activityVC = UIActivityViewController(activityItems: [shareText], applicationActivities: nil)
	present(activityVC, animated: true, completion: nil)
    }
}

プレビュー部分には共有したいテキストが表示されています。

画像を共有する

次に画像を共有してみます。

private func showShareSheet() {
    let shareImage = UIImage(named: "Apple")!
    let activityVC = UIActivityViewController(activityItems: [shareImage], applicationActivities: nil)
    present(activityVC, animated: true, completion: nil)
}

プレビューには共有したい画像が表示されず、アプリアイコンの画像のみが表示されています。
しかし操作を進めると画像は共有できます。

URLを共有する

次にURLを共有してみます。

private func showShareSheet() {
    let shareWebsite = URL(string: "https://www.apple.com/jp")!
    let activityVC = UIActivityViewController(activityItems: [shareImage], applicationActivities: nil)
    present(activityVC, animated: true, completion: nil)
}

プレビューには共有したいURLのOGPとタイトル、URLが表示されています。

データを2つ共有した場合

テキストとURLを共有する

次はテキストとURLを同時に共有してみます。

private func showShareSheet() {
    let shareText = "Apple - Test"
    let shareURL = URL(string: "https://www.apple.com/jp/watch/")!
    let activityVC = UIActivityViewController(activityItems: [shareText, shareURL], applicationActivities: nil)
    present(activityVC, animated: true, completion: nil)
}

URLのプレビューはされずに、テキストのみプレビューされています。

こちらの記事の引用ですが、プレビューされるアイテムには以下のような優先度があるようです。そのためURLよりもテキストの方が優先度は高く、テキストがプレビューされているようです。

UIActivityItemSource > String > URL > Data(UIImage etc.)

優先度について再確認してみる

ただ自分の手元の端末(iOS13.3.1とiOS14.2)で確認したところUIActivityItemSourceよりもStringの方が優先されていました。

自分の手元の端末で優先度を確認したところ以下のようになりました。

優先度
1 String
2 URL/UIActivityItemSource経由のURL
3 Image
4 UIActivityItemSource経由のImage
5 UIActivityItemSource経由のString

「UIActivityItemSource経由のxx」については、以下のようなソースを想定しています。

final class ActivityItemSorce: NSObject, UIActivityItemSource {
    private let text: String // ここの型がURLやUIImageに変わる
    
    init(_ text: String) {
        self.text = text
	super.init()
    }

    // プレビューに表示するURL
    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        text
    }
	
    // 共有したいデータ
    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        text
    }
}

以下のようコードでは、プレビューと共有されるデータがともにURLになってしまいました。

final class ActivityItemSorce: NSObject, UIActivityItemSource {
    private let text: String
    
    init(text: String) {
        self.text = text
	super.init()
    }

    // プレビューしたいデータ
    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        // 共有したいデータがURLだと、StringがプレビューされずURLがプレビューされる
        text
    }
	
    // 共有したいデータ
    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
       URL(string: "https://www.apple.com/jp/")!
    }
}

以上の優先度から「テキストとURLを共有したい」且つ「URLをプレビューしたい」という場合は、以下のように「UIActivityItemSource経由のString」と「URL」を渡すと良いと思います。

private func showShareSheet() {
    let itemSource = ActivityItemSorce("Apple - Test")
    let shareURL = URL(string: "https://www.apple.com/jp/")!
    let activityVC = UIActivityViewController(activityItems: [itemSource, shareURL], applicationActivities: nil)
    present(activityVC, animated: true, completion: nil)
}

本記事では説明しませんが、プレビューをカスタマイズしたい場合はLPLinkMetadataというクラスを利用してください。

LPLinkMetadataはiOS13から利用可能なので注意してください。

共有先のアプリはUTIに依存している

Stringを共有したい場合とURLを共有したい場合では、共有先アプリとして選択可能なアプリが変わると思います。
これは共有先のアプリが、共有したいデータのUTIの受け取りを許容しているか否かに依存しているためです。

UTIとは

wikipediaによると、

Uniform Type Identifier(UTI)はデータ(エンティティ)のタイプ(種類、型)を一意に識別する文字列である
とあります。

例えば、データ(拡張子)がheicの場合はpublic.heicという文字列が割り当てられており、
pngの場合はpublic.pngという文字列が割り当てられています。

またUTIは継承可能であり、親のpublic.imageの子に、public.pngpublic.jpegなどがいます。共有先のアプリ(LINEなど)がpublic.imageのUTIをサポートしているとpngやjpegの画像をすべて共有できるようになると思います。(この部分に関しては検証できていないので、間違っているかもしれません。)
少し前のものですが、OSXv10.4.でサポートしているUTIの一覧は以下になります。

検証してみる

例えばURLを共有する場合とテキストを共有する場合では選択できるアプリが異なってきます。

URLを共有しようとしても、アプリの候補にLINEが出てきません。

Stringを共有しようとすると、アプリの候補にLINEが出てきます。

LINEが対応しているUTIを調べることができないので正しい情報か定かではありませんが、
おそらくUTIがURLのものはサポートしていないためだと思います。

ただURLとStringを同時に共有する場合は候補にLINEが表示されるので、上記の内容は誤っている可能性があります。

まとめ

プレビューの表示に関する優先度については公式ドキュメントにも明記されておらず、どの挙動が正しいのかは分かりませんでした。また実際動作させてみても挙動に一貫性がなく、もやもや感が拭えませんでした。
また共有先の選択可能なアプリは、そのアプリが共有したいデータのUTIをサポートしているかに依存しているので、要件定義する際は注意したいところです。(要件定義後いざ実装しようとした時にこのアプリでは共有できない、という事象が発生しそう。。)

参考URL