LiveViewNative SwiftUI超入門~PhoenixサーバーからiOSアプリのSwiftUIをレンダリング~

# はじめに

本記事はPhoenix LiveViewからHTMLではなく、
SwiftUIのレンダリングするLiveViewNativeSwiftUIの解説と
実際にPhoenixプロジェクトを作成してiOSアプリでHello Worldを表示するところまでを解説します


# LiveViewNativeとは？

PhoenixではWebsocketを介してフロントエンドとバックエンド間の通信を行い、APIを作成せずにサーバーサイドの知識だけでSPAなページを作成できるLiveViewがありますが

それをiOS,Android,Unity,WinUi3等に対してもコネクションを貼ってLiveViewのプロトコルとDSLでかけるようにして覇権とろうぜ！というプロジェクトがLiveViewNativeになります

現在のバージョンは0.4.0-rcで
iOS SwiftUI,Android JetPack,Flutterなどのクライアントライブラリが作成されています

# LiveViewNative.SwiftUIとは？

LiveViewNativeのプロジェクトのなかで一番活発に開発されているライブラリです

XCodeプロジェクトの雛形をxcogegenで生成するジェネレーターやチュートリアル、ドキュメントなど、開発を始めるにあたって必要なものが揃っています

- 基本コンポーネントは網羅
- 独自コンポーネントも実装可能
- コンポーネントの差分更新のパフォーマンスが0.2で大幅に改善
- avkit,photokit,mapkitなども順次ライブラリ化


# 作ってみる

基本こちらに沿って進めていきます

https://github.com/liveview-native/live_view_native

https://github.com/liveview-native/liveview-client-swiftui

## 実行環境
以下の環境で実行しています
macOS Sonoma 14.6.1
CPU Apple M1
elixir 1.17.2-otp-26   
erlang 26.2.5    
phoenix 1.7.18

## プロジェクトの生成

Phoenixが最新版でない方は以下のコマンドで更新しておきましょう

```bash
mix archive.install hex phx_new
```

最新版が入ったら以下のコマンドで通常のPhoenixプロジェクトを作成します
今回はデータベースを使わないので`--no-ecto`オプションをつけて実行します

```
mix phx.new --no-ecto my_app
```

## ライブラリの追加

ライブラリを追加していきます
2025/02/04現在`phoenix_live_view 1.0.3`だとコンパイル時にエラーになるため`1.0.1`で固定します

```diff_elixir:mix.exs
  defp deps do
    [
      {:phoenix, "~> 1.7.18"},
      {:phoenix_html, "~> 4.1"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
-     {:phoenix_live_view, "~> 1.0.0"},      
+     {:phoenix_live_view, "1.0.1"},
      {:floki, ">= 0.30.0", only: :test},
      ...
      {:bandit, "~> 1.5"},
+     {:live_view_native, "~> 0.4.0-rc.0"},
+     {:live_view_native_stylesheet, "~> 0.3.2"},
+     {:live_view_native_swiftui, "~> 0.4.0-rc.0"},
+     {:live_view_native_live_form, "~> 0.4.0-rc.0"}
    ]
  end
```

## セットアップ

コマンドが用意されているので以下を実行します

```
mix lvn.setup.config
```

書き換えの許可を求められるので全部yで書き換えていきます

```
Write to config/config.exs
(Y)es (n)o (d)iff
> y
Write to config/dev.exs
(Y)es (n)o (d)iff
> y
Write to lib/my_app_web/endpoint.ex
(Y)es (n)o (d)iff
> y
Write to lib/my_app_web/router.ex
(Y)es (n)o (d)iff
> y
```

終わったら次のコマンドを実行します

```
mix lvn.setup.gen
```

SwiftUI用のコアコンポーネントとXCodeプロジェクトを作ってくれます

```
Proceed with interactive overwrite? [Yn] y
* creating lib/my_app_native.ex
* creating lib/my_app_web/components/layouts.swiftui.ex
* creating lib/my_app_web/components/layouts_swiftui/app.swiftui.neex
* creating lib/my_app_web/components/layouts_swiftui/root.swiftui.neex
* creating lib/my_app_web/styles/app.swiftui.ex
* creating lib/my_app_web/components/core_components.swiftui.ex
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/1024-mac.png
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/128-mac.png
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/16-mac.png
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/256-mac 1.png
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/256-mac.png
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/32-mac 1.png
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/32-mac.png
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/512-mac 1.png
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/512-mac.png
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/64-mac.png
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/AppIcon 1.jpg
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/AppIcon.jpg
* creating native/swiftui/MyApp/Assets.xcassets/AppIcon.appiconset/Contents.json
* creating native/swiftui/MyApp/Assets.xcassets/Contents.json
* creating native/swiftui/MyApp/ConnectingView.swift
* creating native/swiftui/MyApp/ContentView.swift
* creating native/swiftui/MyApp/DisconnectedView.swift
* creating native/swiftui/MyApp/ErrorView.swift
* creating native/swiftui/MyApp/Preview Content/Preview Assets.xcassets/Contents.json
* creating native/swiftui/MyApp/ReconnectingView.swift
* creating native/swiftui/MyApp/MyApp.swift
* creating native/swiftui/base_spec.yml
* creating native/swiftui/project.yml
* creating native/swiftui/project_watchos.yml
⚙️  Generating plists...
⚙️  Generating project...
⚙️  Writing project...
Created project at /Users/shou/phoenix/elixir_mobile/my_app/native/swiftui/MyApp.xcodeproj

Now that your app is configured please follow these instructions for enabling a LiveView for LiveView Native
https://hexdocs.pm/live_view_native/0.4.0-rc.0/LiveViewNative.html#module-enabling-liveview-for-native
```

テンプレートファイルが`heex`じゃなくて`neex`という拡張子のようです

## LiveViewページの作成

実際に表示するliveviewのページを作っていきます
通常のLiveViewとLiveViewNativeを切り替える起点のhome_live.exを作成します

ついでに render関数を追加して通常のブラウザでも表示できるようにします

```lib/my_app_web/live/home_live.ex
defmodule MyAppWeb.HomeLive do
  use MyAppWeb, :live_view
  use MyAppNative, :live_view

  def render(assigns) do
    ~H"""
    <h1>Hellow Browser</h1>
    """
  end
end
```

## LiveViewNativeページの作成

swiftuiでのアクセスの場合はこちらが表示されます
render関数の第２引数はwatchOS,tvOSなど機種によって表示を変えることが可能です

```lib/my_app_web/live/home_live_swiftui.ex
defmodule MyAppWeb.HomeLive.SwiftUI do
  use MyAppNative, [:render_component, format: :swiftui]

  def render(assigns, _interface) do
    ~LVN"""
    <VStack>
      <Text>
        Hello SwiftUI!
      </Text>
    </VStack>
    """
  end
end
```

## routerへの追加

routerへは、home_liveだけを表示するだけでアーキテクチャごとに表示するliveviewを切り替えてくれます

```diff_elixir:lib/my_app_web/router.ex
  scope "/", MyAppWeb do
    pipe_through :browser

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

準備ができたので以下のコマンドでphoenixサーバーを起動します

```
iex -S mix phx.server
```

## XCodeの起動

`my_app/native/swiftui`配下にファイルが作成されいるので以下のコマンドで開きます

```
open native/swiftui/MyApp.xcodeproj/
```

indexingにめっちゃ時間かかりますが気長に待ちましょう

ビルドしようとしてもエラーが残っていてできませんが、ライブラリを信用しますか？とでてるのでtrustを押しましょう

ビルドが完了したらiOSシミュレーターとブラウザも無事表示されました

![スクリーンショット 2025-02-04 12.39.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/7d4dc04c-34fe-bb40-e794-631bc0496592.png)

# 最後に
フロント側はアーキテクチャ事に分ける必要はありますが
バックエンドとの通信をAPIを介することなく、表示のレイアウト以外を同じElixir/Phoenixでかける
ElixirDesktopと違って外部サーバーとの通信なので、AI/ML系のライブラリも制限なく使えるかと思います

気になった方はチュートリアルがこちらにあるのでぜひやってみてください

https://dockyard.com/blog/2024/09/20/create-your-first-liveview-native-app-part-1

本記事は以上になりますありがとうございました

# 参考サイト
https://hexdocs.pm/live_view_native_swiftui/0.4.0-rc.0/readme.html
https://github.com/liveview-native/live_view_native
https://github.com/liveview-native/liveview-client-swiftui