特定のアプリがインストール済みかチェックする

iOS で特定のアプリがインストールされているかチェックする方法と、それに関する疑問点の調査結果を書いていきます。

# インストール済みかチェック手順

Info.plist に Scheme を登録して、 `canOpenURL` メソッドを実行するだけです。

例えば、 Twitter アプリがインストール済みかチェックするには下記の手順を行います。

## 1. Info.plist に Scheme 登録

![スクリーンショット 2017-07-12 17.08.04.png](https://qiita-image-store.s3.amazonaws.com/0/5678/7f3b6d63-554e-b5ac-9239-b23871346cf1.png)

`LSApplicationQueriesSchemes` というキーで、 Type は `Array` にして、 `twitter` を登録します。

## 2. `canOpenURL` を実行

Twitter のアプリがインストールをした状態で、 `canOpenURL` を実行してみます。

```swift
if UIApplication.shared.canOpenURL(URL(string: "twitter://")!) {
    print("Twitter インストール済み")
 } else {
    print("Twitter インストールされていない")
}
```

実行結果に `Twitter インストール済み` が表示されました。
これで Twitter アプリがインストール済みか特定することができました。

参考: [canOpenURL\(\_:\) \- UIApplication \| Apple Developer Documentation](https://developer.apple.com/documentation/uikit/uiapplication/1622952-canopenurl)

# 疑問点

## Info.plist に書かずに `canOpenURL` を実行したら？

false が返却されるだけで、 warning log やクラッシュはありませんでした。

## Info.plist に書かずに特定のアプリを開こうとしたら？

アプリの Open はできるようです、下記のダイアログが表示されアプリを開くことができました。

![asset_PNG__750×1334_.png](https://qiita-image-store.s3.amazonaws.com/0/5678/4434e40b-84df-bbbf-1ba3-6eaabf02fd02.png)

`LSApplicationQueriesSchemes` はアプリの存在確認に必要なだけで、開く場合はチェックなどは不要なようです。しかし、インストールされていないのにそのアプリを開こうとしても何も起きないので、チェックした上で開かないとユーザ体験がよくないですね。

```swift
let url = URL(string: "twitter://")!
if #available(iOS 10.0, *) {
    UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
    UIApplication.shared.openURL(url)
}
```

iOS 10 以降でアプリを起動するメソッドが変わったようですね。
参考: [\[iOS 10\] UIApplication の openURL: が Deprecated になりました ｜ Developers\.IO](http://dev.classmethod.jp/smartphone/deprecated-openurl/)

このパターンはコメントで指摘いただきました、 @ktysne さんありがとうございます！


## 存在しない Scheme のアプリを開こうとしたら？

存在しない `false` というアプリを開こうとしてみましたが、ログも何も出ず、何も起きませんでした。

```swift
let url = URL(string: "false://")!
if #available(iOS 10.0, *) {
    UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
    UIApplication.shared.openURL(url)
}
```

## Scheme が重複したらどうなる？

本家 Twitter アプリとは別に、 `twitter` という scheme を持った別のサンプルアプリを作って実行してみたところ、 **サンプルアプリが起動しました**。
(後にインストールしたアプリが勝つ?)

セキュアな情報を渡していると、 scheme を重複させられて情報を引っこ抜かれる可能性がありますので注意が必要です。 `canOpenURL` が `true` だとしても自分の望むアプリかは不明なので、全幅の信頼を置くのはやめましょう。

## アプリのアップデートが必須か？

Info.plist を変えるにははアプリのアップデートが必要で、つまり Apple の審査を通さねばなりません。
サーバから scheme をもらって動的に `canOpenURL` の引数を変更はできません。

iOS8 までは `canOpenURL` で他のアプリを調べ放題だったのですが、プライバシーへの配慮で iOS9 以降はこの仕様になったようです。
参考: [iOS9でカスタムURLスキームの遷移に失敗するときの注意点 \- Qiita](http://qiita.com/Night___/items/60355d21951cffbef51b)


## Universal Links でインストール済みか取れないか？

多分ダメ。(多分なのは実際には試していないので...)

Universal Links に対応するにはサーバ + アプリの両方で対応が必要であり、他所のアプリが Universal Links を利用してインストール済みかを取得できません。
また、URL を Safari で開こうすると Universal Links 対応かが自動で判定され、そのアプリが自動で立ち上がります。そのため、自アプリから他所のアプリがインストールされているかの確認はできません。

Universal Links は安全性や非公開性などの思想で設計されているため、これを Hack してインストール状況などは取得不可です。

* 参考
    * [App Searchプログラミングガイド：ユニバーサルリンクへの対応](https://developer.apple.com/jp/documentation/General/Conceptual/AppSearch/UniversalLinks.html)
    * [Universal Linksを試してみました。関連づけファイル\(apple\-app\-site\-association）はS3に置きました。 ｜ Developers\.IO](http://dev.classmethod.jp/smartphone/universal-links/)

## 参考アプリ

### [Launcher \- 通知センターウィジェット搭載ランチャー](https://itunes.apple.com/jp/app/launcher-%E9%80%9A%E7%9F%A5%E3%82%BB%E3%83%B3%E3%82%BF%E3%83%BC%E3%82%A6%E3%82%A3%E3%82%B8%E3%82%A7%E3%83%83%E3%83%88%E6%90%AD%E8%BC%89%E3%83%A9%E3%83%B3%E3%83%81%E3%83%A3%E3%83%BC/id905099592?mt=8)

アプリを開くだけならば Info.plist に書いておく必要はないため、一般的な Scheme をアプリ内に保持して、マイナーなアプリはカスタム Scheme としてユーザが手動で登録できそうです。

# おわり

特定のアプリがインストール済みか調べたい場合、 Info.plist に scheme を登録して `canOpenURL` メソッドを叩く以外にインストール状況の取得はできなさそうです。

あまり変なことをやろうとせずに、素直に Apple 様に従いましょう。