iOSエンジニアのつぶやき

毎朝8:30に iOS 関連の技術について1つぶやいています。まれに釣りについてつぶやく可能性があります。

Swiftでインスタグラムへのシェア導線をつけてみる

この記事でできるもの

下記のキャプチャのようにアプリからインスタグラムのフィードへのシェア導線をつけることができるようになります。

f:id:yum_fishing:20200725002357g:plain

どうやって実装するのか?

結論だけパッとまとめると今回の実装方法は下記の三点セットです。簡単!

  1. アプリでインスタグラムの Custom URL Scheme を処理できることを確認する。
  2. 共有したいコンテンツ(今回は写真)をフォトライブラリーに保存する。
  3. Custom URL Scheme を使ってインスタグラムに遷移する。

概要をつかむ

iOS からインスタグラムにシェアをする方法は主に2つあります。

  • Custom URL Scheme => 今回はこれで実装します!
  • Document Interaction API
    • アプリ間でファイルを共有するためのインターフェース

Custom URL Scheme

Custom URL Scheme を使用する場合は、Info.plist の LSApplicationQueriesSchemes に忘れずに instagram://を追加しましょう。iOS9 からはこれを設定していないとアプリ遷移できないので気をつけましょう。

下記が インスタグラムのシェアに関するドキュメントで記載されていた各 URL の役割になります。でも今回使用するinstagram://libraryについての記載がないのは疑問です 🤔

URL 内容
app Instagramアプリを起動する(アプリが無い場合は?)
camera Instagramアプリをカメラビューで起動、カメラなしのデバイスの場合は写真ライブラリで起動。
media?id= Instagramアプリを起動し、指定されたid値(int)と一致する投稿を読み込む。
user?username= Instagramアプリを起動し、指定されたusername値(string)と一致するInstagramユーザーを読み込む。
location?id= Instagramアプリを起動し、指定されたid値(int)と一致する位置情報フィードを読み込む。
tag?name= Instagramアプリを起動し、指定されたname値(string)と一致するハッシュタグが付けられたページを読み込む。

Document Interaction API

今回はこの方法については詳しく書いていきませんが、下記に参考になるリンクを貼っておきます。

基本的には標準のUIDocumentInteractionControllerのモーダル画面を使ってインスタグラムに画像をシェアしていく方法になります。具体的な実装方法については公式ドキュメントにも載っていますが、下記のようなフローになります。

  1. シェアしたい画像を PNGJPEG(推奨) で Data型に変換します
  2. Data型に変換したものを URL型に変換します。
    • ファイルに保存する際、UTI をcom.instagram.photoにする際は拡張子は必ず.igにします。
    • モダールで表示する共有先アプリ一覧にインスタグラムだけを表示したい場合は、UTI を com.instagram.exclusivegram にして拡張子 igoを使用します。
    • UTI とはデータのタイプを一意に識別する文字列のことです。
  3. 最後に作成した URL からUIDocumentInteractionController を初期化して表示します。

参考

実装してみる

インスタグラムに遷移できるようにする

まずは、アプリからインスタグラムの Custom URL Scheme が利用できるように info.plist の LSApplicationQueriesSchemesinstagram://を追加していきましょう。

画像を保存して LocalIdentifier を取得する

次に共有したい画像をフォトライブラリーに保存しましょう。なぜ一度フォトライブラリーに保存する必要があるのかというと、今回使う instagram://library?LocalIdentifier= ではクエリにLocalIdentifierを使用するためです。このLocalIdentifierPHObject のプロパティである localIdentifierのことをさしています。PhotosFramework を使ったことがある方には馴染みがあるかもしれませんが、端末に入っている画像などを取得する際に受け取ることができる PHAssetPHObject を継承しているので、今回はそれを使ってクエリに値を入れていきます。

    func saveImage() {
        UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
    }

    @objc private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
        let fetchOptions: PHFetchOptions = PHFetchOptions()
        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]

        let fetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
        if (fetchResult.firstObject != nil) {
            guard let lastAsset = fetchResult.lastObject else {
                return
            }
            let localIdentifier = lastAsset.localIdentifier
        }
    }

UIImageWriteToSavedPhotosAlbumの Selector について一点補足をしておくと、この完了ハンドラーにセットするメソッドは指定のシグネチャに準拠している必要があって、引数やメソッドなどが違うとエラーを吐くので気をつけましょう。

参考

LocalIdentifier を使って Custom URL Scheme でインスタグラムに遷移する

上記のコードの続きから書いていきます。インスタグラムの URL Scheme に最新の LocalIdentifier クエリをつけてアプリに Custom URL Scheme での遷移を促すとインスタグラムのフィード画面で指定した画像を開くことができます。

    @objc private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
        let fetchOptions: PHFetchOptions = PHFetchOptions()
        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]

        let fetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
        if (fetchResult.firstObject != nil) {
            guard let lastAsset = fetchResult.lastObject, let urlScheme = URL(string: "instagram://library?LocalIdentifier=\(lastAsset.localIdentifier)") else {
                return
            }
            if UIApplication.shared.canOpenURL(urlScheme) {
                UIApplication.shared.open(urlScheme)
            }
        }
    }

余談

インスタグラムのストーリーに対してのシェア導線の追加実装もちょこっとやってみます。

    func shareToInstagramStory() {
        guard let urlScheme = URL(string: "instagram-stories://share"), let imageData = image.jpegData(compressionQuality: 0.9) else {
            return
        }
        if UIApplication.shared.canOpenURL(urlScheme) {
            UIPasteboard.general.setItems([["com.instagram.sharedSticker.stickerImage": imageData]], options: [:])
            UIApplication.shared.open(urlScheme)
        }
    }

ストーリーに関してはシェアするコンテンツをちゃんと指定しする感じなんですね🤔 これだったら Feed へのシェアもこんな感じで指定できるようにして欲しい感が若干あります。(ドキュメントにちゃんと書かれてない情報も多いし、、) 下記がストーリーでシェアすることのできる一覧です。

コンテンツ キー名 タイプ 説明
バックグラウンド画像アセット* com.instagram.sharedSticker.backgroundImage NSData * サポートされているフォーマット(JPG、PNG)での画像アセットのデータ。最大サイズ720x1280。推奨画像比9:16または9:18。
バックグラウンド動画アセット* com.instagram.sharedSticker.backgroundVideo NSData * サポートされているフォーマット(H.264、H.265、WebM)での動画アセットのデータ。動画は1080p、最長20秒まで対応50MB以下を推奨
スタンプアセット* com.instagram.sharedSticker.stickerImage NSData * サポートされているフォーマット(JPG、PNG)での画像アセットのデータ。推奨画像サイズ:640x480。この画像はスタンプとしてバックグラウンドに表示されます。
バックグラウンドレイヤーのトップカラー com.instagram.sharedSticker.backgroundTopColor NSString * バックグラウンドレイヤーのボトムカラー値と併せて使用される16進数のカラー値。トップカラーとボトムカラーが同じ値である場合は、バックグラウンドレイヤーはソリッドカラーになります。異なる値である場合は、グラデーションカラーとなります。
バックグラウンドレイヤーのボトムカラー com.instagram.sharedSticker.backgroundBottomColor NSString * バックグラウンドレイヤーのボトムカラー値と併せて使用される16進数のカラー値。トップカラーとボトムカラーが同じ値である場合は、バックグラウンドレイヤーはソリッドカラーになります。異なる値である場合は、グラデーションカラーとなります。
アトリビューションURL com.instagram.sharedSticker.contentURL NSString * アプリのコンテンツへのディープリンクURL。指定されない場合、ストーリーにアトリビューションリンクは反映されません。プロトコルを含めた、完全URLを使用します(developers.facebook.comではなくhttps://developers.facebok.com)

アトリビューションURLに関しては現在、クローズドベータ版で、承認された開発者だけが利用できる見たいです。