🍎

Nextjsで作ったアプリをPWABuilderでiOSアプリにしてみた:コーヒードリップ支援アプリ 「BARISTAI」

2023/08/08に公開

作成したアプリの宣伝

この度、コーヒードリップ支援サービスとして作成した「BARISTAI」のiOS版をリリースしました。
https://apps.apple.com/jp/app/baristai/id6451368281

これを使うだけでみなさんのドリップ体験は遥かに良いものになると思います!
ぜひ使ってみてください!

PWABuilderを使用したiOS化の経緯

元々はWebサービスのみで運用していくつもりだったのですが、
「ホーム画面に常駐するアプリの方が生活に溶け込むよね」という結論に至りiOS版を出すことにしました!

iOS化したい。でもSwiftを一から書くのは開発とその後の運用が大変すぎる。どうしよう...
となっていた時にこのPWABuilderの存在を知りました。
「PWAに対応していたら、iOSに変換できるよ!」とのことです。
えーー、めっちゃくちゃ楽やん!そんなん使うしかないやん!
ということで使ってみました。
PWAbuilderの手順に従うだけで簡単にできちゃうのがいいところですね。
ざっくり以下のページにできることとできないことが書いています。
https://blog.pwabuilder.com/posts/publish-your-pwa-to-the-ios-app-store/

一部中身を見ていきます。

What is it?
A web view-based project that enables PWA functionality.
Our platform creates a native Swift app with a WebKit web view to load your PWA while enabling some PWA features, such as service workers, theme color, background color, app icons, in-scope URLs, and more.
それは何ですか?
PWA機能を実現するWebビューベースのプロジェクトです。
当社のプラットフォームは、サービスワーカー、テーマカラー、背景色、アプリアイコン、スコープ内URLなど、いくつかのPWA機能を有効にしながら、PWAをロードするWebKitウェブビューでネイティブSwiftアプリを作成します。

つまり単にWebViewアプリを作るよとのことらしいです。なるほどね。完全に理解した。

ただ最後に以下のようにも書かれていました。

Will Apple approve my PWA?
PWABuilder doesn’t guarantee that your app will be accepted into Apple’s App Store.
Appleは私のPWAを承認しますか?
PWABuilderは、あなたのアプリがAppleのApp Storeに受け入れられることを保証するものではありません。

しかし単にWebViewアプリを作るだけですし、カスタマイズできるよ!とも書いていたので、きっと大丈夫だろうという気持ちで進めていきました。

NextjsをPWA化

まずはNextjsで作ったアプリをPWAに対応させないといけません。
これはもういろんなところで記事出ていると思うので詳細は省きます。
私が参考にしたZennの記事を置いておきます!多分これです。(何個か見たような気もするけどこれに落ち着いた気がします。)
https://zenn.dev/tns_00/articles/next-pwa-install

PWA化ができたら、アプリをどこかしらにデプロイしておかないとPWABuilderは使えません。Vercelかどっかにデプロイしておくようお願いします。

PWABuilderでiOS化

PWA化ができたら次はPWABuilderでiOSにビルドしましょう。
以下が参考です。ここに書いてあるとおり実施すれば問題ないです。
https://docs.pwabuilder.com/#/builder/app-store?id=packaging

簡単に説明すると

  1. 以下のリンクで[Enter the URL to your PWA]にPWA化したアプリのURLを入れます。
    https://www.pwabuilder.com/
    https://storage.googleapis.com/zenn-user-upload/cdd7dd26a0fb-20230807.png

  2. そうすると以下のように表示されます。[Package For Stores]からiOSのパッケージを作成しましょう。

  3. Xcodeで開いて、ビルドするだけでiOSアプリの完成です。
    https://docs.pwabuilder.com/#/builder/app-store?id=building-your-app

3の説明をもう少し書きます。ほとんどドキュメントを日本語にしただけです。

  1. ダウンロードしたパッケージのsrcをターミナルで開きます。
  2. ターミナルでpod installを実行します。brew install cocoapods で前もってpodを使えるようにしておいてください。
  3. Xcodeで.xcworkspaceファイルを開くといい感じに開けます。
  4. Xcode内で Product > Build をクリックして、プロジェクトをビルドできます。

AppStoreへPublish

以下の手順に従って、App StoreにPublishしてください。
ここは手順に従っただけでした。特に引っかかる点はありませんでした。
https://docs.pwabuilder.com/#/builder/app-store?id=publishing

AppStoreからReject

1回目の審査(WebViewアプリを作って提出)

すぐにPublishを実施し、審査に出しました。そして2日ぐらい経ち、結果が出ました。

...Rejectです!
綺麗にRejectされました。Rejectは簡単に書きますが以下のような内容でした。

Guideline 4.2 - Design - Minimum Functionalityに違反しているよ
iOSアプリにする必要ある?ブラウザでいいじゃん!
iOSアプリ出したいなら次やって欲しいことはネイティブ固有の機能を入れてね

とのことでした。
なるほど、じゃあPush通知を入れよう!

2回目の審査(Push通知追加)

今やPWAはiOSでもPush通知を採用しているのですが、PWAbuilderはPush通知機能はサポートしていないようです。以下のようにドキュメントに書いています。

We currently don’t support push notifications. We have partial support in the platform for enabling push notifications via Firebase, but the code is currently commented out, and PWABuilder has no UI for letting you input your push notification details.
If Push Notification support is important to you, upvote this issue.
Also, consider publishing to the Microsoft Store, where your PWA can use Push Notifications and other PWA capabilities using web standards-based code, and without the need of native wrappers.

つまり

UI作らないとPush通知をONにできないんだ。だからコードはあるけどコメントアウトしておくよ!

ということはコメントアウト外すだけで使えるということ?
ということでコードを見てみました。AppDelegate.swiftでコメントアウトされていました。コメントアウト部分を外します。以下が外したあとです。

//パッケージを作成したときにAppDelegate.swiftというファイルがすでに用意されているはずです。

class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window : UIWindow?

    func application(_ application: UIApplication,
                       didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // TODO: if we're using Firebase, uncomment this
        FirebaseApp.configure()

        // [START set_messaging_delegate]
        Messaging.messaging().delegate = self
        // [END set_messaging_delegate]
        // Register for remote notifications. This shows a permission dialog on first run, to
        // show the dialog at a more appropriate time move this registration accordingly.
        // [START register_for_notifications]
   
        UNUserNotificationCenter.current().delegate = self

	let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
        UNUserNotificationCenter.current().requestAuthorization(
            options: authOptions,
            completionHandler: {_, _ in })


        application.registerForRemoteNotifications()

        // [END register_for_notifications]
        return true
      }

ただこれだけだとPush通知を送るためのサーバーがありません。そこで使うのが
Firebase Cloud Message
です。これも簡単でした。ググって使ってください。GCPのドキュメントに従えば一発だと思います。
簡単に説明すると

  1. プロジェクト追加
    https://firebase.google.com/docs/cloud-messaging/ios/client?hl=ja#add_firebase_to_your_apple_project

  2. APNs認証キーをアップロード
    https://firebase.google.com/docs/cloud-messaging/ios/client?hl=ja#upload_your_apns_authentication_key

そしてFCMを作成できたら、設定ファイルを取得してください。
それをiOSアプリのプロジェクトにあるGoogleService-Info.plistと交換します。

あら不思議、これでPush通知が完成です。
FCM上でテストしてみるためにキャンペーンを作成してみました。

しっかりと通知ができていますね!
では、審査に追加してみましょう!
次の日に審査が開始されました。早いですね。

結果Rejectでした。
Guideline 4.2 - Design - Minimum Functionalityに違反している
です!
メッセージの内容をみると
Push通知だけじゃ足りないよ!他にもネイティブな機能を追加してね!
とのことです。
これはもう多少はSwiftを書かないといけないということですね。

3回目の審査(デバイス内保存・自動ロック制御 追加)

二つネイティブアプリにしたらやりたかったことで、すぐにできそうなことがありました。それは以下の二つです。

  1. スマホの自動ロックをタイマー起動中はOFFにする
  2. 一個前に開いたレシピをデバイスに保存しておき、次回アプリ起動した時に開けるようにする

1つめはIdleTimerの制御でできるみたいです。
コードはどうなるのでしょうか?
まずはWebフロントエンド側です。
WebViewで起動したWebフロントエンドではwindowにmessageHandlersというクライアントがインジェクションされるらしく。それを使用してネイティブアプリとWebフロントエンドを通信させているらしいです。

// ネイティブアプリ側にkeepScreenOnというメッセージを送る。
export const startOnScreen = () => {
    if (
      typeof window !== 'undefined' &&
      window.webkit &&
      window.webkit.messageHandlers &&
      window.webkit.messageHandlers.keepScreenOn
    ) {
      window.webkit.messageHandlers.keepScreenOn.postMessage()
      console.log('start')
    }
  }
  
//ネイティブアプリ側にstopKeepScreenOnというメッセージを送る。
  export const stopOnScreen = () => {
    if (
      typeof window !== 'undefined' &&
      window.webkit &&
      window.webkit.messageHandlers &&
      window.webkit.messageHandlers.stopKeepScreenOn
    ) {
      window.webkit.messageHandlers.stopKeepScreenOn.postMessage(
        'stopKeepScreenOn',
      )
      console.log('start')
    }
  }

上記の関数を自動ロックをオフにしたい時とオンにしたい時に実行することで制御します。
では次にSwiftのコードを見ましょう。

//パッケージを作成したときにViewController.swiftというファイルがすでに用意されているはずです。

extension ViewController: WKScriptMessageHandler {
  func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "print" {
            printView(webView: BARISTAI.webView)
        }
        if message.name == "push-subscribe" {
            handleSubscribeTouch(message: message)
        }
        if message.name == "push-permission-request" {
            handlePushPermission()
        }
        if message.name == "push-permission-state" {
            handlePushState()
        }
	// JavascriptからkeepScreenOnというメッセージが送られたら
	// 自動ロックをOFFにする。
        if message.name == "keepScreenOn" { 
          UIApplication.shared.isIdleTimerDisabled = true
          print(message.body)
        }
	// JavascriptからstopKeepScreenOnというメッセージが送られたら
	// 自動ロックをOFFにする。
        if message.name == "stopKeepScreenOn" {
          UIApplication.shared.isIdleTimerDisabled = false
          print(message.body)
        }

2つ目のデバイス保存に関しては大体1つ目と同じような方法で実装したので割愛します。

流石にここまでやったら大丈夫だろうと思い、再度審査に提出しました。

そして審査通過

流石にここまで実施したら審査が通過しました!
実施したことをまとめると以下の4つのことを実施するとPWA BuilderでもAppStoreの審査に通るかもしれません!

  1. PWA BuilderでWebViewアプリを作成。
  2. Push通知追加
  3. デバイス内保存追加
  4. 自動ロック制御追加

Rejectは2回されたものの、割とスムーズに進められたのはAppleのレビュワーさんのおかげかもしれません
ネットで調べると、「Guideline 4.2のRejectは何を修正すればいいかわからないから辛い」、「Appleのレビュワーはメッセージの返信義務がないから返信がこないことがある」などが書かれていたのですが、私のレビュワーさんはすごく返信も早かったですし、方針もしっかりと伝えてくれました。
運が良かったのかもしれません。

最後に

フロントエンドを実施したことがある人であれば、正直ここまでやるのは難しくないと思います。
iOSアプリ作ったことないけど、WebアプリをiOSにしたい人はぜひ試してみるといいのではないでしょうか?

現在BARISTAIは私と友人の二人で開発をしています。
追加して欲しい機能などどんどん募集しておりますので、@hudebakonosoto
もしくは以下の問い合わせからご連絡ください!
https://docs.google.com/forms/d/15T28xUPg07iyoUikAAisk6ePwAHcavgV9fiPlhuoPH4

BARISTAIのアプリは、iOS の方は以下のリンクからダウンロードしてみてください!
https://apps.apple.com/jp/app/baristai/id6451368281

Androidの方はPWAに対応しているのでWebブラウザからinstallいただけますのでぜひインストールしてみてください!
http://baristai.net

Discussion