LiveViewNative × Swift で ネイティブアプリ作成

# 概要

Elixirでスマートフォンアプリを開発する際に、**LiveViewNative**というライブラリを利用することができます。

似たような選択肢として**ElixirDesktop**がありますが、両者には次のような大きな違いがあります。

* アプリケーションの実装形式の違い
    * LiveViewNative：ネイティブアプリ
    * ElixirDesktop：WebViewベースのアプリ
* ページの表示方法の違い
    * LiveViewNative：外部公開されたPhoenixサーバーのページにアクセスして表示
    * ElixirDesktop：ローカルで起動したサーバーのページにアクセスして表示

LiveViewNativeでは、ネイティブクライアントごとに異なるライブラリを使用することで、<font color="red">1つのPhoenixプロジェクトから複数のOS向けモバイルアプリ</font>を作成することが可能です。

今回はネイティブクライアントとして**SwiftUI**を選択し、iOSアプリを作成していきます。

また記事中の内容は下記のリポジトリで公開しています

https://github.com/BlackTanuTanuNinjas/tutorial_lvn

# 環境

本記事の内容はMac環境で検証しています。
使用しているElixirやErlangのバージョンは以下の通りです。

```zsh
% asdf current
Name            Version         Source                       Installed
elixir          1.18.2-otp-27   /Users/linsei/.tool-versions true
erlang          27.2.2          /Users/linsei/.tool-versions true
nodejs          22.7.0          /Users/linsei/.tool-versions true
```

# 手順

LiveViewNativeでは、サーバー側とクライアント側で実装が分かれます。
以下はネイティブクライアントに、SwiftUIを選択した場合になります。

* サーバー側：Phoenixプロジェクト
* クライアント側：Xcode（SwiftUI）プロジェクト

それぞれのプロジェクト作成手順については後述しますが、基本的なディレクトリ構成は以下のようになります。

```zsh
tutorial_lvn % tree -L 1
. ← これがサーバー側
├── (略)
└── native
    └── swiftui
        ├── TutorialLvn
        └── TutorialLvn.xcodeproj ← これがクライアント側
```

このあと、サーバー側・クライアント側に分けて手順を紹介していきます。

## サーバー側

サーバー側の実装は、以下の手順で進めていきます。

1. [Phoenixプロジェクトの作成](#phoenixプロジェクトの作成)  
2. [LiveViewNativeの依存関係の追加](#liveviewnativeの依存関係の追加)  
3. [LiveViewNativeのセットアップ](#liveviewnativeのセットアップ)

## Phoenixプロジェクトの作成

まずはPhoenixプロジェクトを作成します。
今回は将来的にデータベースを使った内容も扱う可能性があるため、Ectoありで生成します。

```zsh
% mix phx.new tutorial_lvn
```

## LiveViewNativeの依存関係の追加

LiveViewNativeを使用するために、公式リポジトリの手順に従って依存関係を追加します。

https://github.com/liveview-native/live_view_native?tab=readme-ov-file#getting-started

mix.exs の deps セクションに以下を追記してください。

```diff_elixir:mix.exs
  defp deps do
    [
      # LiveViewNative(+Swift)の依存を追加
+      {:live_view_native, "~> 0.4.0-rc.1"},
+      {:live_view_native_stylesheet, "~> 0.4.0-rc.1"},
+      {:live_view_native_swiftui, "~> 0.4.0-rc.1"},
+      {:live_view_native_live_form, "~> 0.4.0-rc.1"}
    ]
  end
```

## LiveViewNativeのセットアップ

依存関係の追加・取得が完了したら、以下のコマンドを順に実行してLiveViewNativeのセットアップを行います。

```zsh
% mix lvn.setup
To setup your application with LiveView Native run:

> mix lvn.setup.config
> mix lvn.setup.gen

% mix lvn.setup.config
(略)
Don't forget to run mix lvn.setup.gen

% mix lvn.setup.gen
(略)
```

これらのコマンドを実行すると、Xcodeプロジェクトが自動生成されます。
ネイティブクライアントにSwiftUIを選択した場合は、この生成されたプロジェクトを編集して開発を進めていきます。

## クライアント側

今回はSwiftUIを使用するため、公式リポジトリのSwiftUI向けリポジトリを参考に進めていきます。

https://github.com/liveview-native/live_view_native?tab=readme-ov-file#native-clients

クライアント側の実装は、以下の手順で進めます。

1. [クライアントの追加](#クライアントの追加)  
2. [SwiftUIモジュールの作成](#swiftuiモジュールの作成)  
3. [neexファイルへの内容記述](#neexファイルへの内容記述)  
4. [`routerex`の修正](#routerexの修正)  
5. [Xcodeプロジェクトを開く](#xcodeプロジェクトを開く)  
6. [接続先サーバーのURLを設定](#接続先サーバーのurlを設定)  
7. [アプリの起動](#アプリの起動)

### クライアントの追加

XcodeプロジェクトにSwiftUIのクライアントを追加するには、以下のコマンドを実行します。

```zsh
mix lvn.swiftui.gen
```

ただし、SwiftUIクライアントはサーバー側のセットアップ時に自動生成されているため、通常はこの手順をスキップして問題ありません。

### SwiftUIモジュールを作成する

今回は、`HomeLive.SwiftUI`というSwiftUIモジュールを作成します。
以下のコマンドを実行してください。

```zsh
% mix lvn.gen.live swiftui Home
```

このコマンドを実行すると、`/live`ディレクトリ内に次のファイルが生成されます

* `home_live.swiftui.ex`
* `swiftui/home_live.swiftui.neex`

続いて、同じディレクトリに`home_live.ex`を作成し、以下のように記述します。

```elixir:lib/tutorial_lvn_web/live/home_live.ex
defmodule TutorialLvnWeb.HomeLive do
  use TutorialLvnWeb, :live_view
  use TutorialLvnNative, :live_view
end
```

最終的な`/live`ディレクトリの構成は次のようになります。

```
lib/tutorial_lvn_web/live
├── home_live.ex
├── home_live.swiftui.ex
└── swiftui
    └── home_live.swiftui.neex
```

### neexに内容を記述

画面を表示した際にテキストが表示されるよう、neex ファイルに以下のように記述します。

```swift:lib/tutorial_lvn_web/live/swiftui/home_live.swiftui.neex
<Text>Hello, SwiftUI!</Text>
```

### `router.ex`を変更する

`/`にアクセスした際、SwiftUIクライアントでneexが読み込まれるよう、ルーティングを変更します。

```diff_elixir:lib/tutorial_lvn_web/router.ex
  scope "/", TutorialLvnWeb do
    pipe_through :browser

-   get "/", PageController, :home
+   live "/", HomeLive
  end
```

### Xcodeでプロジェクトを開く

ここまでの準備が完了したら、Xcodeを起動し、生成されたプロジェクトを開きます。
今回のプロジェクトは`native/swiftui/TutorialLvn.xcodeproj`に作成されています。

初回の読み込み時には、Xcode側でビルド設定などの内部処理が走るため、少し時間がかかる場合があります。

この処理には少し時間がかかりますが、以降の手順を進めているうちにちょうど完了することが多いので、焦らず進めていきましょう。

### 接続先サーバーのURLを指定する

SwiftUIクライアントがPhoenixサーバーへアクセスできるように、接続先URLを設定します。
この作業は以下の2ステップに分かれます

* Phoenixサーバーを外部からアクセスできるように公開する
* 公開されたURLをSwiftUIクライアントに設定する

#### Phoenixサーバーを外部からアクセスできるように公開する

まずは、SwiftUIクライアントがアクセスできるように、Phoenixサーバーを外部公開する準備を行います。
今回は ngrok を使って、ローカルで起動したサーバーを外部に公開します。

```zsh
# サーバーを起動する
% mix phx.server

# 別ターミナルで ngrok を起動する
% ngrok http 4000
Forwarding   https://bea9-125-13-7-104.ngrok-free.app -> http://localhost:4000
```

この例では`https://bea9-125-13-7-104.ngrok-free.app`が外部からアクセス可能なURLになります。
このURLを、後ほどSwiftUIクライアントの接続先として設定します。

#### 公開されたURLをSwiftUIクライアントに設定する

次に、Xcodeプロジェクト内で、外部公開したPhoenixサーバーのURLを指定します。

`TutorialLvn/ContentView`ファイルを開き、`#LiveView`の設定部分を以下のように変更してください


```diff_swift:native/swiftui/TutorialLvn/ContentView
        #LiveView(
            .automatic(
-               development: .localhost(path: "/"),
+               development: URL(string: "https://bea9-125-13-7-104.ngrok-free.app")!,
-               production: URL(string: "https://example.com")!
+               production: URL(string: "https://bea9-125-13-7-104.ngrok-free.app")!
            ),
            addons: [
               .liveForm
            ]
        )
```

この変更により、アプリはngrok経由でPhoenixサーバーに接続し、neexファイルを読み込んで画面を描画できるようになります。

### アプリを起動する

ここまでの設定が完了したら、Xcodeでアプリをビルドして実行してみましょう。

実行時に`Signing for "TutorialLvn" ...`のようなエラーが表示された場合は、 Xcode上部のプロジェクト設定 → Signing & Capabilities タブで、Apple ID に紐づく開発チームを選択してください。

# 動作確認

iPhone SE（実機）および Apple Watch（エミュレーター）で動作確認を行いました。

iPhone SE（実機）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455723/e3e82afe-d84e-4bb6-abca-863a639ff273.jpeg" width="50%" />

Apple Watch

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455723/d1495677-050e-49dd-80fd-522c1cb6902b.png" width="50%" />

# まとめ

今回はLiveViewNativeを使ってiOSアプリを作成しました。

ElixirDesktop以外でのスマホアプリ開発手法を試すことで、新たな選択肢を知る良い機会になりました。

また、Apple Watchのエミュレーターでも動作確認を行えたことで、Watchアプリを開発する手法についても触れることができました。

LiveViewNativeの学習を深めていけば、将来的にWatchアプリの開発が決まった時に、**さまざまな機能を開発者目線で提案できるようになる**かもしれません。

次回は、neex上の要素の操作や、ElixirとSwiftの処理連携について掘り下げ、より実用的な内容を紹介する予定です。

# 関連

https://qiita.com/B_tanuki/items/5e110b6f49228fa4a265