KinoLiveViewNative

Mix.install(
  [{:kino_live_view_native, path: "./"}],
  # [{:kino_live_view_native,  "~> 0.2.2"}],
  config: [
    kino_live_view_native: [
      {ServerWeb.Endpoint,
       [
         server: true,
         url: [host: "localhost"],
         adapter: Phoenix.Endpoint.Cowboy2Adapter,
         render_errors: [
           formats: [html: ServerWeb.ErrorHTML, json: ServerWeb.ErrorJSON],
           layout: false
         ],
         pubsub_server: Server.PubSub,
         live_view: [signing_salt: "JSgdVVL6"],
         http: [ip: {127, 0, 0, 1}, port: 4000],
         secret_key_base: String.duplicate("a", 64),
         live_reload: [
           patterns: [
             ~r/#{__ENV__.file |> String.split("#") |> hd()}$/
           ]
         ]
       ]}
    ],
    kino: [
      group_leader: Process.group_leader()
    ],
    live_view_native: [plugins: [LiveViewNative.SwiftUI]],
    live_view_native_stylesheet: [parsers: [swiftui: LiveViewNative.SwiftUI.RulesParser]]
  ],
  force: true
)

Quickstart

To use the KinoLiveViewNative project we need to install it. You can include the following in your Notebook dependencies and setup section of Livebook.

Mix.install(
  [{:kino_live_view_native,  "~> 0.2.2"}],
  config: [
    server: [
      {ServerWeb.Endpoint,
       [
         server: true,
         url: [host: "localhost"],
         adapter: Phoenix.Endpoint.Cowboy2Adapter,
         render_errors: [
           formats: [html: ServerWeb.ErrorHTML, json: ServerWeb.ErrorJSON],
           layout: false
         ],
         pubsub_server: Server.PubSub,
         live_view: [signing_salt: "JSgdVVL6"],
         http: [ip: {127, 0, 0, 1}, port: 4000],
         secret_key_base: String.duplicate("a", 64),
         live_reload: [
           patterns: [
             ~r/#{__ENV__.file |> String.split("#") |> hd()}$/
           ]
         ]
       ]}
    ],
    kino: [
      group_leader: Process.group_leader()
    ],
    live_view_native: [plugins: [LiveViewNative.SwiftUI]],
    live_view_native_stylesheet: [parsers: [swiftui: LiveViewNative.SwiftUI.RulesParser]]
  ],
  force: true
)

By default, the server starts on port 4000. You could change the configuration if you wish.

Basic LiveView Native Example

Let's create a basic LiveView that will be available at http://localhost:4000. You can also inspect the native template code at http://localhost:4000/?_lvn[format]=swiftui

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive do
  use ServerWeb, :live_view

  @impl true
  def render(%{format: :swiftui} = assigns) do
    ~SWIFTUI"""
    <Text>Hello from LiveView Native!</Text>
    """
  end

  def render(assigns) do
    ~H"""
    <p>Hello from LiveView!</p>
    """
  end
end
|> Server.SmartCells.LiveViewNative.register("/", ":index")

import Server.Livebook, only: []
import Kernel
:ok

Live Reloading

KinoLiveViewNative uses automatic code reloading, so anytime you change this file or evaluate one of the LiveView Native smart cells, the server will hot reload the page.

Evaluate the cell below that changes the text, and you should see the application reload in your simulator.

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive do
  use ServerWeb, :live_view

  @impl true
  def render(%{format: :swiftui} = assigns) do
    ~SWIFTUI"""
    <Text>Hello again from LiveView Native!</Text>
    """
  end

  def render(assigns) do
    ~H"""
    <p>Hello again from LiveView!</p>
    """
  end
end
|> Server.SmartCells.LiveViewNative.register("/", ":index")

import Server.Livebook, only: []
import Kernel
:ok

Asset Paths

Asset paths are relative to the host URL. We have provided an example image at http://localhost:4000/images/logo.png for you to use in any examples.

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive do
  use ServerWeb, :live_view

  @impl true
  def render(%{format: :swiftui} = assigns) do
    ~SWIFTUI"""
    <AsyncImage url="/images/logo.png"/>
    """
  end
end
|> Server.SmartCells.LiveViewNative.register("/", ":index")

import Server.Livebook, only: []
import Kernel
:ok

Core Components

Core components are supported.

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive do
  use ServerWeb, :live_view

  def render(assigns) do
    ~H"""
    <.button>Click</.button>
    """
  end
end
|> Server.SmartCells.LiveViewNative.register("/", ":index")

import Server.Livebook, only: []
import Kernel
:ok

Tailwind

While tailwind is included in the project, it is not currently functional.

Tailwind does static analysis to find classes used in the project, and therefore does not find classes defined within Livebook files.

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive do
  use ServerWeb, :live_view

  def render(assigns) do
    ~H"""
    <p class="text-red">Not Red Text</p>
    """
  end
end
|> Server.SmartCells.LiveViewNative.register("/", ":index")

import Server.Livebook, only: []
import Kernel
:ok

Bindings

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive do
  use ServerWeb, :live_view

  @impl true
  def render(%{format: :swiftui} = assigns) do
    ~SWIFTUI"""
    <Button phx-click="ping">Hello from LiveView Native!</Button>
    """
  end

  def render(assigns) do
    ~H"""
    <button phx-click="ping">Hello from LiveView!</button>
    """
  end

  @impl true
  def handle_event("ping", _params, socket) do
    IO.inspect("PONG")
    {:noreply, socket}
  end
end
|> Server.SmartCells.LiveViewNative.register("/", ":index")

import Server.Livebook, only: []
import Kernel
:ok