Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: Hotwire Nativeでネイティブモバイルアプリを作ろう: iOS編パート1(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

従来Turbo NativeとStradaと呼ばれていたものは、現在はHotwire Nativeに統合されました。

参考: Hotwire Native: Hotwire Native is a web-first framework for building native mobile apps.

hotwired/hotwire-native-ios - GitHub

Rails: Hotwire Nativeでネイティブモバイルアプリを作ろう: iOS編パート1(翻訳)

私のブログ記事で人気があるシリーズは、Turbo Nativeの導入と実行に関するものでした。私は、純粋にビジネスの観点からこのライブラリを熱烈に愛しています。専任のネイティブアプリチームを招集せずに済み、モバイルプラットフォームごとに構築する画面を同じにするための苦労が不要になるので、時間とコストを大幅に節約できます。

Rails World 2024の開催直前にこの方面でエキサイティングな進展が見られました。37signalsと、あの伝説のJoe Masilottiが、Hotwire Nativeをリリースしたのです。

Hotwire Nativeは、開発者の人間工学の観点から、従来のturbo-native-iosライブラリとturbo-native-androidライブラリから完全な進化を遂げ、Webアプリにネイティブコンパニオンアプリを設定するのがこれまで以上に簡単になりました。

そこで、本チュートリアルシリーズ記事では、iOSから始めてAndroidへと進める形でHotwire Nativeについて詳しく見ていくことにします。

🔗 背景(読み飛ばしても構いません)

読者のほとんどはHotwireについて既にひととおりご存知だと思いますが、手短に背景を説明しておきます。

Hotwireは、「Web」「iOS」「Android」を対象とするライブラリのコレクションで、サーバー側でレンダリングするHTMLをマークアップする際の人間工学を重視しています。
Hotwireの方法論は、ReactなどのSPA(シングルページアプリケーション)フレームワークがHTMLをオンザフライで生成し、JSONエンドポイントからの入力に基づいて状態の変更に「反応(react)」することが多いのとは対照的です。

Hotwireの中核にあるturbo.jsというライブラリは、ブラウザのページ全体を再読み込みする必要も、キャッシュなどの追加機能の実行も必要もなしに、ページナビゲーションを処理します。

turbo.jsのようなライブラリを利用するメリットは、SPAについて回る開発上の欠点を取り除いて、SPAのさまざまな長所を得られることです。

🔗 Railsアプリをセットアップしてみよう

Hotwire Nativeはturbo.jsと共生関係にあるので、ページナビゲーションをturbo.jsで処理しているアプリなら問題なく利用できます。
turbo.jsは任意のバックエンドフレームワークで利用可能ですが、Railsではデフォルトで付属しています。

アプリがまだない方は、さくっと作ってみましょう。本記事ではRails 7.2以降を利用することを前提としています。

rails new hotwire_native_todo

scaffoldで手早く作成を済ませましょう。

rails g scaffold Todo title:string complete:boolean

マイグレーションも実行します。

rails db:migrate

それではToDoのindexページをhomeページに設定しましょう。
config/routes.rbファイルにroot "todos#index"を追加します。

# config/routes.rb
Rails.application.routes.draw do
  resources :todos
  root "todos#index"
end

最後に、アプリケーションレイアウトにsimplecssを追加してスタイルを手早く整えましょう。

<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
  <head>
   ...
    <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
  </head>
<body>
   ...

それではRailsサーバーを起動して、いよいよiOSアプリに取り掛かることにしましょう。

bin/rails s

ブラウザでlocalhost:3000を実行すると、できたてのRailsアプリが表示されるはずです。

🔗 iOSアプリをセットアップする

iOSアプリのセットアップには、いくつかの手順を実行する必要があります。

  • 1. Xcode を開き、「File」→「New」→「Project」で新しい iOS アプリを作成します。
    このとき、SwiftUIではなく、必ず「Storyboard」を選択すること。

2. プロジェクトの保存場所を指定して「Create」をクリックします。

Configure Xcode project

🔗 Hotwire Nativeに統合する

次に、「File」 → 「Add Packages Dependencies」Hotwire Nativeパッケージを追加し、検索フィールドにhttps://github.com/hotwired/hotwire-native-iosと入力します。プロジェクトが「Add to Project」で正しく設定されていることを確認したら、「Add Package」をクリックします。

Configure Xcode project

パッケージのダウンロードが完了したら、「Add to Target」列の下のドロップダウンで、作成したアプリ名を選択して、「Add Package」をクリックします。

最後に、SceneDelegateファイルを開いて、内容を次のコードにすべて置き換えます。

import HotwireNative
import UIKit
let rootURL = URL(string: "http://localhost:3000")!

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  var window: UIWindow?

  private let navigator = Navigator()

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
      guard let windowScene = scene as? UIWindowScene else { return }
      window = UIWindow(windowScene: windowScene)

      window?.rootViewController = navigator.rootViewController
      window?.makeKeyAndVisible()

      navigator.route(rootURL)
  }
}

次は、localhostからのトラフィック受信を許可する設定をアプリに追加しましょう。

Info.plistを開いて画面を右クリックし、「Add Row」をクリックします。

ドロップダウンで「App Transport Security Settings」を選択します。次に「+」をクリックして、ドロップダウンで「Allow Arbitrary Loads」を選択し、この項目をYESに設定します。

次に、「Application Scene Manifest」>「Scene Configuration」で"Storyboard name"を削除します。

変更後のInfo.plistは以下のようになります。

これで、Runボタンをクリックするかコマンド+Rキーを押して、できたてのiOSアプリを起動すれば、サーバーサイドHTMLがすべて表示されているのが見えるはずです。

🔗 パスを設定してもう少しネイティブらしくする

Hotwire Nativeでは多くの機能をすぐに利用できますが、さらに一歩先に進むことも可能です。

/new/editで終わるルーティングの各フォームに、適切なモーダルを表示してみましょう。こういうときは、パス構成(Path Configuration)を導入するのがベストです。

パス構成とは、ネイティブアプリまたはサーバー(またはその両方)に存在するファイルで、特定のコンテンツをネイティブアプリでどのように表示するかを指示できます。

path-configuration.jsonという新しいファイルを作成して、以下の内容を追加します。

{
  "settings": {},
  "rules": [
    {
      "patterns": [
        ".*"
      ],
      "properties": {
        "context": "default",
        "pull_to_refresh_enabled": true
      }
    },
    {
      "patterns": [
        "/new$", "/edit$"
      ],
      "properties": {
        "context": "modal",
        "pull_to_refresh_enabled": false
      }
    }
  ]
}

次に、Navigatorクラスでパス構成ファイルについて指示します。AppDelegateファイルの内容を以下に置き換えます。

import HotwireNative
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {



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

        let localPathConfigURL = Bundle.main.url(forResource: "path-configuration", withExtension: "json")!

        Hotwire.loadPathConfiguration(from: [
            .file(localPathConfigURL)

        ])
        return true
    }

    // MARK: UISceneSession Lifecycle

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }


}

アプリを再度ビルドして実行(コマンド+Rキー)すると、/new/editで終わるルーティングに移動するたびにモーダルポップアップが表示されるはずです。


次回は、iOSアプリにネイティブ画面を追加する方法について解説します。

関連記事

Rails: HotwireCombobox gemが素晴らしすぎるという話(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。