カスタムプラグインを使ってアルバムアプリを作ろう！

## はじめに

スマートフォンでよく使う機能に、カメラ、そしてアルバム（ギャラリー）があります。
Monacaで標準的に使えるカメラプラグインを使えば、カメラはもちろん、アルバムからも写真を選ぶことが出来るのですが、この写真選択はデフォルトの形式でしかできなくて、LineやFacebook Messengerのように、スタイリッシュに画像を並べてそこから選択する、ということが出来ません。

そこで、カスタムプラグインを利用することが条件となりますが、より自由にアルバムのデータを扱うアプリの作り方について紹介します。

利用するプラグインは、`cordova-plugin-photo-library` です。

なお、デバイスのアルバムに格納されているすべての写真にアクセスしてしまうので、利用には十分注意してください。開発時には、ふだん使いではない、サンプルの画像のみしかないデバイスを使用することをおすすめします。

## プラグインの説明

`cordova-plugin-photo-library`のリポジトリはhttps://github.com/terikon/cordova-plugin-photo-library です。現在の最新版のmasterのコミット番号は`a587f6effbbdf1648af72f0cb25c13d4fb008d3b`です。バージョンが上がると動作しなかったり予想外の動作をする可能性があるため、Monaca (Cordova 6.5)で実行する場合は、上記のコミット番号を明示した方が良いかもしれません。（プラグインのインポート時のURLの末尾に、#をつけてコミット番号を入れると、そのコミット番号のものが使われます。つまり、URLとして`https://github.com/terikon/cordova-plugin-photo-library.git#a587f6effbbdf1648af72f0cb25c13d4fb008d3b`を指定します)

まずは、プラグインのREADME.mdを読んで見てください。

このプラグインは大きく分けて、3つの機能があります。

1. アルバムから画像を取得する
2. アルバムにアクセスするPermissionを許可するダイアログを出す
3. 画像をアルバムに保存する

ここでは、1.の機能について取り上げて見ます。また、1.の機能を利用するためには、必然的に2.の機能も使うことになります。

ところで、実はこのプラグインの`plugin.xml`をよく見ると、iosのディレクティブに

```xml
<dependency id="cordova-plugin-add-swift-support" version="1.6.0"/>
```

と書いてあります。そう、実はこのプラグインのiOS部分は、swiftで書いてあるのです。
swiftで記述されたプラグインは、比較的めずらしいのですが、実は残念ながら、そのままではこれをMonacaから利用することが出来ません。そこで、これをMonacaで利用する方法についても説明します。

## MonacaでSwiftプラグインを利用する方法 (iOS)

__2018/04/19追記__ cordova-plugin-add-swift-support 1.7.2で、この記事内で説明している箇所が修正されたようです。そのため、1.7.2以上を指定した場合であれば、Monacaでの利用が可能です。

そもそも、なぜこの`cordova-plugin-add-swift-support`が必要なのでしょうか？
Monacaで作られるアプリは、iOSの場合、そのランタイムとしてCordova (CordovaLib)が使われているのですが、実はこのCordovaLibはObjective-Cで記述されています。そして一般に、Objecitve-Cからswiftのコードを呼び出すためには、BridgeHeaderという仕組みを使う必要があります。(他にもフレームワーク化してバイナリを利用するという方法もあります）

そして、`cordova-plugin-add-swift-support`が、このBridgeHeaderの仕組みを担ってくれるのですが、、、実は、このプラグインはそのままではMonacaでは利用出来ません。なぜかというと、このプラグインはiOSのプラットフォームバージョンを取得して処理を分けているのですが、このプラットフォームバージョンを取得するのに利用している

```javascript

const platformMetadata = context.requireCordovaModule('cordova-lib/src/cordova/platform_metadata');
 ...
platformMetadata.getPlatformVersions(projectRoot).then((platformVersions) => {
 // platformVersionsを使った処理
}
```
という部分で、platformVersionsがうまく取得出来ないためです。これは、Cordovaの仕様で、`cordova platform add`したときに、カスタムディレクトリを指定すると、そのプロジェクトで取得した`platformVersions`がsemver形式の文字列ではなく、ディレクトリ名そのものになってしまうということに原因があります。

なので、この問題を改修したものを、
https://github.com/knight9999/cordova-plugin-add-swift-support
に用意しました。こちらを使えば、この問題は改善されます。
（この修正は、プルリクエスト
https://github.com/akofman/cordova-plugin-add-swift-support/pull/36
を投げているのですが、残念ながら、Cordova本体側でgetPlatformVersionsを修正するまで待つということでした、、、__2018/04/18追記__ version 1.7.2でこれと同様の機能が組み込まれました）

Monacaのプロジェクトを作ったら、まず最初にカスタムプラグインとして、
https://github.com/knight9999/cordova-plugin-add-swift-support
を登録してください。その次に、
https://github.com/terikon/cordova-plugin-photo-library
を登録してください。<font color="red">この順番は大切です</font>。先にcordova-plugin-photo-libraryを入れてしまうと、オリジナルの`cordova-plugin-add-swift-support`が読み込まれてしまい、ビルドに失敗してしまいます。

なお、Androidでしか試さないのであれば、`cordova-plugin-add-swift-support`プラグインはいれなくてもOKです。

## アプリの作成

話が長くなってしまいましたが、アプリを作成していきましょう。今回のアプリは、ただ一つのボタンが表示されているアプリで、そのボタンを押すと、アルバムの画像が画面に次々と表示されるものとします。

まず、HTML部分ですが、head部のCSP宣言を次のようにしてください

```html
    <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: content: https://ssl.gstatic.com; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'; img-src * data: content: blob: cdvphotolibrary:;">
```

最後に追加されている`img-src`のところが重要です。`content:`を許可するとともに、`cordova-plugin-photo-library`のドキュメントに説明されているように、`cdvphotolibrary:`も許可しておきます。

そして、body部ですが、次のようにします。

```html
<body>
	<br />
    Gallery App
    <br />
    <br />
    <button onclick="loadPhotos()">Load Gallery</button>
    <br />
    <br />
    <div id="gallery"></div>
</body>
```

シンプルですね？

## JS部分

次に、JS部分ですが、これはhead部にある`script`タグに次のように記述します。

```html
    <script>
        function loadPhotos(retry) {
            var gallery = document.getElementById('gallery');
            while (gallery.firstChild) gallery.removeChild(gallery.firstChild);
            cordova.plugins.photoLibrary.getLibrary(
                function (chunk) {
                    var library = chunk.library;
                    library.forEach(function (libraryItem) {
                        var img = document.createElement('img');
                        img.setAttribute('src',libraryItem.thumbnailURL);
                        img.setAttribute('width','100%');
                        gallery.appendChild(img);
                    });
                }, function(err) {
                 if (!retry || err.startsWith('Permission')) {
                     requestAuthorization();
                 }  else {
                     console.log('Error in getLibrary: ' + err);
                 }
                }, {
                    chunkTimeSec: 0.3
                }
            );
        }
        function requestAuthorization() {
            cordova.plugins.photoLibrary.requestAuthorization(
                function() {
                    loadPhotos(true); //Retry
                },
                function (err) {
                    var gallery = document.getElementById('gallery');
                    gallery.innerHTML = "Auth Error";
                }, {
                    read: true,
                    write: false
                }
            );
        }
    </script>
```

関数が二つ定義されているだけですが、それぞれ見て行きましょう。

最初に定義されている`loadPhotos`は、ボタンをタップしたときに呼ばれる関数です。
まず、idがgalleryのdivタグを取得して、その中身を空にしています。
次に、photoLibraryのプラグインを使って、チャンクと、そのチャンクの画像を取得していきます。
取得した画像は、imgタグのsrcとして貼り付けて、galleryの子要素として追加していきます。
ただし、もしこの処理中にエラーが起こった場合、その原因がPermissionであれば、もう一つの関数である`requestAuthorization`を呼び出します。

次に、二番目の関数である`requestAuthorization`についてですが、こちらは、「読み込み」許可をsystem経由でユーザーに確認しています。もし許可が成功すれば、再度、`loadPhotos`関数を実行します。無限ループにならないように、retryのフラグをtrueにした状態で呼び出しています。
　許可が失敗した場合は、`Auth Error`と表示します。


## 全コード

簡単なアプリのため、全コードをあげておきます。

```html
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: content: https://ssl.gstatic.com; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'; img-src * data: content: blob: cdvphotolibrary:;">
    <script src="components/loader.js"></script>
    <link rel="stylesheet" href="components/loader.css">
    <link rel="stylesheet" href="css/style.css">
    <script>
        function loadPhotos(retry) {
            var gallery = document.getElementById('gallery');
            while (gallery.firstChild) gallery.removeChild(gallery.firstChild);
            cordova.plugins.photoLibrary.getLibrary(
                function (chunk) {
                    var library = chunk.library;
                    library.forEach(function (libraryItem) {
                        var img = document.createElement('img');
                        img.setAttribute('src',libraryItem.thumbnailURL);
                        img.setAttribute('width','100%');
                        gallery.appendChild(img);
                    });
                }, function(err) {
                 if (!retry || err.startsWith('Permission')) {
                     requestAuthorization();
                 }  else {
                     console.log('Error in getLibrary: ' + err);
                 }
                }, {
                    chunkTimeSec: 0.3
                }
            );
        }
        function requestAuthorization() {
            cordova.plugins.photoLibrary.requestAuthorization(
                function() {
                    loadPhotos(true); //Retry
                },
                function (err) {
                    var gallery = document.getElementById('gallery');
                    gallery.innerHTML = "Auth Error";
                }, {
                    read: true,
                    write: false
                }
            );
        }
    </script>
</head>
<body>
	<br />
    Gallery App
    <br />
    <br />
    <button onclick="loadPhotos()">Load Gallery</button>
    <br />
    <br />
    <div id="gallery"></div>
</body>
</html>
```