[Firebase][iOS] Firebase Hosting でキャンペーンページを作ってみよう

はじめに

モバイルアプリサービス部の中安です。

Firebaseを触ってみるシリーズ」の続きになります。

ここまでは

というお題で書かせていただきましたが、

今回はFirebase の静的ウェブホスティングサービスであるFirebase Hostingを触ってみようと思います。

またもウダウダと書きますが、何かのお役に立てば幸いです。

Firebase Hosting

Firebase Hostingは、ホスティングされたWebページやWebアプリを簡単な操作でデプロイできちゃうサービスです。

アプリではWebページ(しかも簡易的な)で済ませたい機能はよくあります。 たとえば、利用規約やプライバシーポリシーなどといった約款系であったり、FAQのような静的でありながら運用上更新がかかるような画面、 そして今回のお題のような何かをお知らせするようなキャンペーンページなどでしょうか。

Webページであれば、iOSのみならずAndroidアプリにも流用が簡単です。

簡単な上に高速性、安心性があるならば、 アプリ開発者としてはわざわざサーバを立てたり借りたりするよりもサクッと良い感じに作れるので 選択肢のひとつに十分なりうると思います。

以下は公式ドキュメントから引用したFirebase Hostingの特長です。

  • 安全な接続を介して配信する: 最新のウェブは優れたセキュリティを備えています。Firebase Hostingには構成が不要のSSLが組み込まれているため、コンテンツを常に安全に配信できます。
  • コンテンツを高速に配信する: アップロードしたファイルは世界中のCDNエッジにあるSSDにキャッシュされます。ユーザーがどこにいても、コンテンツを高速に配信できます。
  • 高速なデプロイFirebase CLIを使用して、わずか数秒でアプリを稼働させることができます。コマンドラインツールにより、デプロイターゲットを簡単にビルドプロセスに追加できます。
  • ワンクリックのロールバック: 迅速なデプロイは大いに有用ですが、間違いを元に戻せることはさらに優れた利点です。Firebase Hostingはワンクリックでロールバックできる完全なバージョン管理とリリース管理を提供しています。

うん、素敵。

準備

Xcodeプロジェクト側

基本的にWebページを扱うので、SDKなどの準備は不要かと思います。 httpsでのやりとりになりますが、もしもApp Transport Security(ATS)に何らかの制限をかけている場合は、そのあたりをご注意くださいませ。

Firebase CLI

Firebase Hostingへのデプロイは前述にちらっと出てきましたが、Firebase CLI (Command-Line Interface)というコマンドベースのツールを使って行います。 Firebase Hosting以外のサービスにも使用するツールではありますが、この記事シリーズでは初出なのでその準備方法を書いておきます。

申し訳ないのですが、当記事ではMacのターミナルで作業する前提で進めていきます。 Windows等については他の諸記事や公式ドキュメントなどを参照ください。

node.jsとnpmは入っていますか?

Firebase CLI自体は、Node.jsのモジュールパッケージ管理ツールである「npm (Node Package Manager)」を使用してインストールします。 もし、未インストールの場合は先にそちらをインストールしておいてください。

インストール済みかどうかはバージョンを確認することで分かると思います。

Node.jsの確認 (2019年2月現在 Node.jsは5.10.0以降が必要)

$ node -v

npmの確認

$ npm -v

CLIのインストール

下のコマンド一発ポンっです。そのまましばし待たれよ

$ npm install -g firebase-tools

終わったら念の為確認します

$ firebase --version

バージョン番号が出てきたら大丈夫と思います。

ウェブページコンテンツ

今回の主役はWebページです。 サンプルなので簡易的なものを用意します。

秋葉原に誕生した架空のカフェ「でででっぱーかふぇ」のお知らせページです。
(実際のカフェとは関係ありません)

<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>でででっぱーかふぇオープンのお知らせ</title>
</head>

<body>
	<header>
	    <h1>でででっぱーかふぇオープンのお知らせ</h1>
	</header>
	<section>
		<article>
		 	<p>
		   	11月1日に「でででっぱーかふぇオープン」が秋葉原にオープンしました
		 	</p>
		</article>
	</section>
	<footer>
		<address>Copyright(C) でででっぱーかふぇ All right reserved.</address>
	</footer>
</body>
</html>
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>コーヒー価格値下げのお知らせ</title>
</head>

<body>
	<header>
	    <h1>コーヒー価格値下げのお知らせ</h1>
	</header>
	<section>
		<article>
		 	<p>
		   	12月1日よりコーヒーの価格を300円から270円に値下げいたします。この機会にぜひお越しください。
		 	</p>
		</article>
	</section>
	<footer>
		<address>Copyright(C) でででっぱーかふぇ All right reserved.</address>
	</footer>
</body>
</html>

デプロイしてみる

では、用意したHTMLファイルを実際にデプロイしてみたいと思います。

Firebase CLIを使うためにターミナルを開きます。

Firebaseにログイン

まず、Firebase CLIからFirebaseにログインします。

$ firebase login

すでにブラウザでGoogleアカウントにログインしていると、 Already logged in as (アカウントのメールアドレス)と返ってくるかもしれません。 その場合は次のイニシャライズの項に進んでください。

では、初回ログイン時の話をします。

まず、Allow Firebase to collect anonymous CLI usage and error reporting information? (Y/n) と質問されると思いますが、 こちらは「Firebaseの使用状況やエラーを匿名で収集していい?」とのことなので、「まぁいいでしょう」とYを選択するでいいと思います。

すると、次にブラウザが立ち上がってGoogleのログイン画面が自動的にローディングされると思います。 ログインするとFirebase CLIに対するスコープの許可を下図のように聞かれます。

確認しつつ許可しましょう。

このように成功したらターミナルに戻ります。 Success! Logged in as (アカウントのメールアドレス)と表示されているはずです。

イニシャライズ

次に、Firebase CLIを使ってFirebase Hostingのための初期化処理をしてあげる必要があります。

ホスティングするファイルの置き場のルートにしたい新規フォルダを適当なところに作り、 ターミナルからそのフォルダへと移動します。

$ cd (作ったフォルダのパス)

イニシャライズのコマンドを実行します。

$ firebase init

すると下記のようなFirebase CLIとの対話が始まります。


     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /Users/(ディレクトリパス)

? Which Firebase CLI features do you want to setup for this folder? Press Space to select features, then Enter to confirm your c
hoices. (Press <space> to select)
❯◯ Database: Deploy Firebase Realtime Database Rules
 ◯ Firestore: Deploy rules and create indexes for Firestore
 ◯ Functions: Configure and deploy Cloud Functions
 ◯ Hosting: Configure and deploy Firebase Hosting sites
 ◯ Storage: Deploy Cloud Storage security rules

「このフォルダでセットアップするのはどの機能であるか?」と聞かれているので、 矢印をHosting:に合わせ、スペースキー押下で選択状態にしてエンターキーをタァーンします。

色々と何かが行われた後にまた質問がきます。

? Select a default Firebase project for this directory: (Use arrow keys)
❯ [don't setup a default project] 
  project-name-a (ProjectA) 
  project-name-b (ProjectB) 
  project-name-c (ProjectC) 
  [create a new project] 

「このディレクトリでデフォルトで使うプロジェクトは何かね?」と聞かれるので、 対象のFirebaseプロジェクトを選びます。

? What do you want to use as your public directory? (public) 

Firebase Hostingで公開されるコンテンツのディレクトリ」を聞かれます。 好みで変えてもいいですが、今回はデフォルトのpublicのままにしようと思うので、このままエンターキーをタァーンします。

? Configure as a single-page app (rewrite all urls to /index.html)? (y/N) 

「よかったらSPAに適した設定してあげるけど、どう?」と聞かれるので、今回はパスするのでNをタァーンします。

そうすると、Firebase CLIは準備を終えて終了します。

中身を見てみます。

$ ls -la
drwxr-xr-x   6 hoge  staff   192  2 21 15:21 .
drwxr-xr-x  13 hoge  staff   416  2 21 15:08 ..
-rw-r--r--   1 hoge    55  2 21 15:21 .firebaserc
-rw-r--r--   1 hoge  1144  2 21 15:21 .gitignore
-rw-r--r--   1 hoge   134  2 21 15:21 firebase.json
drwxr-xr-x   4 hoge   128  2 21 15:21 public

このように、firebase.jsonを含む色々なファイルと、publicディレクトリが自動的に配置されました。 publicディレクトリの中にはindex.htmlなどが置かれていると思います。

デプロイ

先ほど作った2つのHTMLファイルをpublicのディレクトリに移動させて、ディレクトリは移動せずに下記のコマンドを打ちます。

$ firebase deploy

たったこれだけ。あら簡単。デプロイが始まります。

=== Deploying to '(プロジェクト)'...

i  deploying hosting
i  hosting[(プロジェクト)]: beginning deploy...
i  hosting[(プロジェクト)]: found 4 files in public
✔  hosting[(プロジェクト)]: file upload complete
i  hosting[(プロジェクト)]: finalizing version...
✔  hosting[(プロジェクト)]: version finalized
i  hosting[(プロジェクト)]: releasing new version...
✔  hosting[(プロジェクト)]: release complete

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/(プロジェクト)/overview
Hosting URL: https://(プロジェクト).firebaseapp.com

Deploy complete!と出ましたね。 最後にホスティングのURLとしてhttps://(プロジェクト).firebaseapp.comと書かれているので、試しに開いてみます。

$ open https://(プロジェクト).firebaseapp.com/shop_open.html

ブラウザで確認できました。おめでとうございます

キャンペーン画面を作る

ここまでできたら「Firebase Hostingの基本的な使い方はできましたね」という感じですが、 今回のお題は「キャンペーンページを作ってみる」なので、もう少しアプリを絡めた実務的な使い方を書いていきたいと思います。

Firestoreにキャンペーンを作る

ここでいうキャンペーンとは「運用者がアプリユーザにお知らせしたいコンテンツ」とします。 「運用者がキャンペーンを登録して、それをユーザがアプリで見る」という流れにするために、 Cloud Firestoreにキャンペーン情報を貯めておくことにします。

Cloud Firestoreについては、こちらに書いています。

データを作る

図のようにcampaignsというコレクションを作り、その中に手入力で情報を書いていきます。

ドキュメントIDは自動採番で、タイトルと先ほどホスティングしたURLを入れてみました。

2つページを用意したので、同構造のデータをもう1つ作っておきます。

ルール設定

ルールはとりあえずは誰でも見れるという設定にしておきます。

service cloud.firestore {
  match /databases/{database}/documents {
   match /campaigns/{document=**} {
     allow read
   }
  }
}

アプリ側でキャンペーン一覧画面を作る。

iOSアプリ側を作っていきたいと思います。

サンプルなので、デザインは画面一杯にテーブルビューが置かれているだけのUIにします。

ビューコントローラ

キャンペーン一覧画面としてCampaignViewControllerを用意しました。 空っぽの実装ですが、テーブルビューの処理は事前にこのようにしておきます。

import UIKit

class CampaignViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet private weak var tableView: UITableView!
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 0 // あとで実装
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        // あとで実装
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        // あとで実装
    }
}

インポート

キャンペーンの詳細は、Firestoreで先ほど設定したurlの値を取得し、Safariビューコントローラで表示することにします。

import UIKit
import Firebase
import SafariServices

このようにインポートさせてやります。

データ取得

今回はviewDidLoad()のタイミングでFirestoreへのデータ取得をしにきます。

private var data = [[String : Any]]()

override func viewDidLoad() {
    super.viewDidLoad()
    
    let db = Firestore.firestore()
    db.collection("campaigns").getDocuments() { [weak self] querySnapshot, error in
        guard let self = self else { return }
        
        if let error = error {
            // エラー処理
            self.data = []
            self.tableView.reloadData()
            return
        }
        
        if let querySnapshot = querySnapshot {
            self.data = querySnapshot.documents.map { snapshot in
                return snapshot.data()
            }
            self.tableView.reloadData()
        }
    }
}

コレクション参照オブジェクトの getDocuments()で複数のドキュメントを参照しに行っています。 コールバックではクエリスナップショットが返され、その中に取得できたドキュメントのデータが入っているので、 それを保持しつつテーブルビューをリロードします。

データ表示

Firestoreドキュメントのデータはディクショナリで渡されてきます。 Campaignクラスといったデータオブジェクトのクラスでラッピングしてあげるほうがベターと思いますが、 今回はそのまま使います。

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return data.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    let datum = data[indexPath.row]
    cell.textLabel?.text = datum["title"] as? String
    return cell
}

詳細を表示

テーブルビューのセルが選ばれたら詳細を表示するようにします。

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    
    let datum = data[indexPath.row]
    let urlString = datum["url"] as! String
    let url = URL(string: urlString)!
    let view = SFSafariViewController(url: url)
    present(view, animated: true, completion: nil)
}

SFSafariViewControllerFirestoreで設定されたURLを渡して開きます。

動作確認

このようにFirestoreに登録したデータが一覧に表示されます。

セルをタップでHostingにデプロイしたWebページが表示されます。

このようにキャンペーン一覧からキャンペーン詳細を見ることのできるアプリとなりました。

最後に

簡素なサンプルでしたが、Webページを簡単に公開することができました。

多くのリソースをホスティングしたり、同時にたくさんのユーザが訪れるようなWebページを考えている場合は、 無料枠だと制限があることに注意してください。

詳しくは Firebase Hosting のリファレンスを参照ください