【iOS】ShareExtension を導入してみたよ

どうも。Reo です。

個人アプリに ShareExtension を使うついでに、手順をメモしながら導入しようと思います。

 

環境

  • Xcode11.3
  • Swift 5.1.3
  • iOS 13.3

今回はサンプルプロジェクトを用意しません。余裕があるときに追記するかもしれませんが。

 

ターゲットを追加する

まずはターゲットを追加します。

ShareExtension は別ターゲットで開発していくことになります。なのでライブラリ等を使う場合は注意が必要です。

File > New > Target より ShareExtension を追加します。

名前はそのまま ShareExtension にしました。

作成するとポップアップが出てきました。Activate を選択しましょう。

 

これで ShareExtension フォルダができていればおkです。

簡単!

とりあえずビルドをしてみましょう。

ビルドターゲットを ShareExtension に設定をして実行します。

すると、どのアプリで実行するかのポップアップが表示されます。とりあえず Safari で見てみましょう。

Safari が開かれるので 下部タブバーのシェアボタンを押してみます。

はい!プロジェクト名のアイコン(設定してないので白ペッペ)を見つけられたらオッケーです。

押してみると、デフォルトの画面が表示されます。

ここの画面をカスタマイズして、アプリに情報を送信することができます。今は Post に何もアクションを設定してないので、画面を閉じる処理のみが行われます。

また、Cancel, Post の部分はアプリをローカライズしている場合には反映がされるはずです。ちょっと試してないんですが、されるはず…

 

こんな風に画面をカスタマイズすることもできますよ。

AppAppより

全く手入れができていないんですが、AppApp のコードは GitHub に公開してあります。

 

AppGroups の設定をする

さて。ShareExtension とアプリを繋げるために AppGroups の設定をしていきます。

プロジェクトを選択して、TARGETS > ShareExtension の 上部タブの Sigining & Capabilities を開きます。+アイコンを押して Capability を追加します。(Capabilityは能力という意味。能力の追加…!カコイイ!)

 

AppGroups をダブルクリックで追加します。

そうすると以下のような項目が追加されます。

 

これね、すごいね。AppAppが出てきちゃった!!

App Groups を利用すると、FileManager や UserDefaults を使ってアプリ間でファイルの共有をすることができます。でも今回は使わないのでここには特にチェックを入れず、新規に Group を作ります。

+を押して、Group を追加します。group. プロジェクトのBundle ID にすると良いです。

間違えた名前で作ってしまった奴が消せない…消せないのかこいつ…ちょっとわかったら追記します。

 

同じ作業を Targets > メインアプリの方でも行ってください!

 

Keychain Sharingを追加する

AppGroups を追加する手順と同様に Keychain Sharing を追加します。

こちらは+ボタンを押すだけで追加されます。

 

これでとりあえずおkのはず。

 

UserDefaults で保存してみる

最後に実際にデータを UserDefaults で保存して、アプリで受け取ることができるかを試してみます。

ShareViewController.swift の didSelectPost() 内で URL を取得します。Safari で検証する予定なので、URL が取得できる前提です。

didSelectPost() は投稿ボタンが押されたときの処理を書きます。

 


override func didSelectPost() {
    guard let extensionItem: NSExtensionItem = extensionContext?.inputItems.first as? NSExtensionItem, let itemProviders = extensionItem.attachments else { return }
    // URL を取得する
    let identifier = "public.url"
    let urlProvider = itemProviders.first(where: { $0.hasItemConformingToTypeIdentifier(identifier)})
    urlProvider?.loadItem(forTypeIdentifier: identifier, options: nil, completionHandler: { [weak self] (item, error) in
        // item に格納されている
        guard let url = item as? URL else { return }
        self?.save(url: url)
        // 最後にこれを呼ばないとフリーズする
        self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
    })
}

private func save(url: URL) {
    // UserDefaults に保存
}

この辺の説明はちょっと割愛。いずれAppAppのリファクタリングブログを書くつもりなのでその時に余裕があれば…(書く書く詐欺なう…)

 

save(url:)の中で、UserDefaultsに保存します。

private func save(url: URL) {
    // AppGroups に設定したもの
    let suiteName = "group.hoge.hoge.hogehoge"
    let key = "shareSampleURLKey"
    let userDefaults = UserDefaults(suiteName: suiteName)
    userDefaults?.set(url, forKey: key)
}

 

これで ShareExtension を実行して Post まで実行します。

アプリ側の適当な ViewController で取得してみます。

final class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // AppGroups に設定したもの
        let suiteName = "group.hoge.hoge.hogehoge"
        let key = "shareSampleURLKey"
        let userDefaults = UserDefaults(suiteName: suiteName)
        let value = userDefaults?.url(forKey: key)
        print(value)
    }
}

 

これで取得ができていればおkです。

nil だと …

 

AppGroups の設定を Targets > メインアプリの方 でも同様に設定が必要でした!!!!

AppGroupsの項でも追記しておきました。

 

無事取得できたぞ…

 

extensionContext.completeRequest を呼ばなかったら…

以前、AppAppを作っていた時は extensionContext.completeRequest を呼び忘れるとフリーズしてしまいます。

フリーズというよりかは SLComposeViewController が閉じてくれません。

iOS13.3で確認すると、デフォルトでモーダル表示になるので、下にスワイプすると画面を閉じることができます。そうじゃない場合には呼び出すのを忘れると、閉じることができなくなります。

 

Info.plist で取得コンテンツを制限する

今のままだと以下のような Warning が表示されます。

Embedded binary’s NSExtensionActivationRule is TRUEPREDICATE. Before you submit your containing app to the App Store, be sure to replace all uses of TRUEPREDICATE with specific predicate statements or NSExtensionActivationRule keys. If any extensions in your containing app include TRUEPREDICATE, the app will be rejected.

 

ShareExtension の Info.plist で修正します。

変更前

 

NSExtensionActivationRule の型を Dictionary に変更します。

その中に必要な情報のみを書きましょう。

今回はURLのみを使いたいので

NSExtensionActivationSupportsWebURLWithMaxCount | Number | 1

を設定します。

変更後

 

これでURLのみが取得できるようになります。

 

おわりに

少しぐだってしまった。メモがてら書くつもりが、記事を書く方がメインになってしまっていました。

そして、ほとんど「Share Extensionでデータを共有する – Qiita」の記事を見てやってたようなものなので、実質書く意味もなかった気がしますね…

Xcodeのバージョンが変わって、AppGroups の追加方法が若干変わっているので、その辺ぐらいですかねー。

 

とりあえず今回はこの辺で。画像ばっかりで重くなるのでブログを軽くすることも考えないと…

ではでは〜

Comments...

コメントは認証制です。詳しくは下記の注意をお読みください。お気軽にコメントお願いします!

Write a Comment

コメント時の注意

「Twitter」「Facebook」「Google+」「WordPress」のいずれかのアカウントをお持ちの方は各アカウントと連携することでコメントできます。 コメントしたことはSNSに流れませんので、アカウントをお持ちの方はこちらの方法でコメントを投稿して下さると嬉しいです。 アカウントをお持ちでない方はメールアドレスで投稿することができます。 初回コメント時は承認後に表示されます。

Related Memo...

記事を書くほどでもないけれどメモっておきたいこと

テスト投稿。

例えばiphone7 の画面サイズ

750 × 1334
半分375 × 667

iOS

UITableView.RowAnimation の .none はアニメーションするよ

UITableView.RowAnimation の .none はアニメーションがnoneなわけじゃなく、デフォルトの設定を使うよという意味らしい。

The inserted or deleted rows use the default animations.

なのでアニメーションしちゃう。今更の気づき。

 

iOS

UINavigationController + UIScrollView の組み合わせで使っている時に謎の余白ができる時

UINavigationController + UIScrollView の組み合わせで使っていて、UIScrollView 上に AutoLayout で上下左右0で View を設置しているのに、30px程度上にずれてしまうとき。

`navigationController.navigationBar.isTranslucent = false` にすると直るかもしれない。

ScrollView上のコンテンツとNavigationBarの重なっているところが透過していたら多分これで直せるはず。

通常のターゲットではちゃんと動いているのに、iOSSnapshotTestCase を用いたテストでだけこの対応が必要なのよくわからないけれど。。。

iOS
more