View Source SwiftUI Views
Overview
LiveView Native aims to use minimal SwiftUI code and use the same patterns for building interactive UIs as LiveView. However, unlike LiveView for the web, LiveView Native uses SwiftUI templates to build the native UI.
This lesson will teach you how to build SwiftUI templates using common SwiftUI views. We'll cover common uses of each view and give you practical examples you can use to build your own native UIs. This lesson is like a recipe book you can refer back to whenever you need an example of how to use a particular SwiftUI view. In addition, once you understand how to convert these views into the LiveView Native DSL, you should have the tools to convert essentially any SwiftUI View into the LiveView Native DSL.
Render Components
LiveView Native 0.3.0
introduced render components to better encourage isolation of native and web templates and move away from co-location templates within the same LiveView module.
Render components are namespaced under the main LiveView, and are responsible for defining the render/1
callback function that returns the native template.
For example, and ExampleLive
LiveView module would have an ExampleLive.SwiftUI
render component module for the native Template.
This ExampleLive.SwiftUI
render component may define a render/1
callback function as seen below.
# Render Component
defmodule ServerWeb.ExampleLive.SwiftUI do
use ServerNative, [:render_component, format: :swiftui]
def render(assigns) do
~LVN"""
<Text>Hello, from LiveView Native!</Text>
"""
end
end
# LiveView
defmodule ServerWeb.ExampleLive do
use ServerWeb, :live_view
use ServerNative, :live_view
@impl true
def render(assigns) do
~H"""
<p>Hello from LiveView!</p>
"""
end
end
Throughout this and further material we'll re-define render components you can evaluate and see reflected in your Xcode iOS simulator.
defmodule ServerWeb.ExampleLive.SwiftUI do
use ServerNative, [:render_component, format: :swiftui]
def render(assigns, _interface) do
~LVN"""
<Text>Hello, from a LiveView Native Render Component!</Text>
"""
end
end
Embedding Templates
Alternatively, you may omit the render callback and instead define a .neex
(Native + Embedded Elixir) template.
By default, the module above would look for a template in the swiftui/example_live*
path relative to the module's location. You can see the LiveViewNative.Component
documentation for further explanation.
For the sake of ease when working in Livebook, we'll prefer defining the render/1
callback. However, we recommend you generally prefer template files when working locally in Phoenix LiveView Native projects.
SwiftUI Views
In SwiftUI, a "View" is like a building block for what you see on your app's screen. It can be something simple like text or an image, or something more complex like a layout with multiple elements. Views are the pieces that make up your app's user interface.
Here's an example Text
view that represents a text element.
Text("Hamlet")
LiveView Native uses the following syntax to represent the view above.
<Text>Hamlet</Text>
SwiftUI provides a wide range of Views that can be used in native templates. You can find a full reference of these views in the SwiftUI Documentation at https://developer.apple.com/documentation/swiftui/. You can also find a shorthand on how to convert SwiftUI syntax into the LiveView Native DLS in the LiveView Native Syntax Conversion Cheatsheet.
Text
We've already seen the Text view, but we'll start simple to get the interactive tutorial running.
Evaluate the cell below, then in Xcode, Start the iOS application you created in the Create a SwiftUI Application lesson and ensure you see the "Hello, from LiveView Native!"
text.
defmodule ServerWeb.ExampleLive.SwiftUI do
use ServerNative, [:render_component, format: :swiftui]
def render(assigns) do
~LVN"""
<Text>Hello, from LiveView Native!</Text>
"""
end
end
HStack and VStack
SwiftUI includes many Layout container views you can use to arrange your user Interface. Here are a few of the most commonly used:
Below, we've created a simple 3X3 game board to demonstrate how to use VStack
and HStack
to build a layout of horizontal rows in a single vertical column.o
Here's a diagram to demonstrate how these rows and columns create our desired layout.
flowchart
subgraph VStack
direction TB
subgraph H1[HStack]
direction LR
1[O] --> 2[X] --> 3[X]
end
subgraph H2[HStack]
direction LR
4[X] --> 5[O] --> 6[O]
end
subgraph H3[HStack]
direction LR
7[X] --> 8[X] --> 9[O]
end
H1 --> H2 --> H3
end
Evaluate the example below and view the working 3X3 layout in your Xcode simulator.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<VStack>
<HStack>
<Text>O</Text>
<Text>X</Text>
<Text>X</Text>
</HStack>
<HStack>
<Text>X</Text>
<Text>O</Text>
<Text>O</Text>
</HStack>
<HStack>
<Text>X</Text>
<Text>X</Text>
<Text>O</Text>
</HStack>
</VStack>
"""
end
end
Your Turn: 3x3 board using columns
In the cell below, use VStack
and HStack
to create a 3X3 board using 3 columns instead of 3 rows as demonstrated above. The arrangement of X
and O
does not matter, however the content will not be properly aligned if you do not have exactly one character in each Text
element.
flowchart
subgraph HStack
direction LR
subgraph V1[VStack]
direction TB
1[O] --> 2[X] --> 3[X]
end
subgraph V2[VStack]
direction TB
4[X] --> 5[O] --> 6[O]
end
subgraph V3[VStack]
direction TB
7[X] --> 8[X] --> 9[O]
end
V1 --> V2 --> V3
end
Example Solution
defmodule ServerWeb.ExampleLive.SwiftUI do
use ServerNative, [:render_component, format: :swiftui]
def render(assigns, _interface) do
~LVN"""
<HStack>
<VStack>
<Text>O</Text>
<Text>X</Text>
<Text>X</Text>
</VStack>
<VStack>
<Text>X</Text>
<Text>O</Text>
<Text>O</Text>
</VStack>
<VStack>
<Text>X</Text>
<Text>X</Text>
<Text>O</Text>
</VStack>
</HStack>
"""
end
end
Enter Your Solution Below
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<!-- Enter solution below -->
"""
end
end
Grid
VStack
and HStack
do not provide vertical-alignment between horizontal rows. Notice in the following example that the rows/columns of the 3X3 board are not aligned, just centered.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<VStack>
<HStack>
<Text>X</Text>
<Text>X</Text>
</HStack>
<HStack>
<Text>X</Text>
<Text>O</Text>
<Text>O</Text>
</HStack>
<HStack>
<Text>X</Text>
<Text>O</Text>
</HStack>
</VStack>
"""
end
end
Fortunately, we have a few common elements for creating a grid-based layout.
- Grid: A grid that arranges its child views in rows and columns that you specify.
- GridRow: A view that arranges its children in a horizontal line.
A grid layout vertically and horizontally aligns elements in the grid based on the number of elements in each row.
Evaluate the example below and notice that rows and columns are aligned.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<Grid>
<GridRow>
<Text>XX</Text>
<Text>X</Text>
<Text>X</Text>
</GridRow>
<GridRow>
<Text>X</Text>
<Text>X</Text>
</GridRow>
<GridRow>
<Text>X</Text>
<Text>X</Text>
<Text>X</Text>
</GridRow>
</Grid>
"""
end
end
List
The SwiftUI List view provides a system-specific interface, and has better performance for large amounts of scrolling elements.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<List>
<Text>Item 1</Text>
<Text>Item 2</Text>
<Text>Item 3</Text>
</List>
"""
end
end
Multi-dimensional lists
Alternatively we can separate children within a List
view in a Section
view as seen in the example below. Views in the Section
can have the template
attribute with a "header"
or "footer"
value which controls how the content is displayed above or below the section.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<List>
<Section>
<Text template="header">Header</Text>
Content
<Text template="footer">Footer</Text>
</Section>
</List>
"""
end
end
ScrollView
The SwiftUI ScrollView displays content within a scrollable region. ScrollView is often used in combination with LazyHStack, LazyVStack, LazyHGrid, and LazyVGrid to create scrollable layouts optimized for displaying large amounts of data.
While ScrollView
also works with typical VStack
and HStack
views, they are not optimal choices for large amounts of data.
ScrollView with VStack
Here's an example using a ScrollView
and a HStack
to create scrollable text arranged horizontally.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<ScrollView>
<VStack>
<Text :for={n <- 1..100}>Item <%= n %></Text>
</VStack>
</ScrollView>
"""
end
end
ScrollView with HStack
By default, the axes of a ScrollView
is vertical. To make a horizontal ScrollView
, set the axes
attribute to "horizontal"
as seen in the example below.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<ScrollView axes="horizontal">
<HStack>
<Text :for={n <- 1..100}>Item <%= n %></Text>
</HStack>
</ScrollView>
"""
end
end
Optimized ScrollView with LazyHStack and LazyVStack
VStack
and HStack
are inefficient for large amounts of data because they render every child view. To demonstrate this, evaluate the example below. You should experience lag when you attempt to scroll.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<ScrollView>
<VStack>
<Text :for={n <- 1..10000}>Item <%= n %></Text>
</VStack>
</ScrollView>
"""
end
end
To resolve the performance problem for large amounts of data, you can use the Lazy views. Lazy views only create items as needed. Items won't be rendered until they are present on the screen.
The next example demonstrates how using LazyVStack
instead of VStack
resolves the performance issue.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<ScrollView>
<LazyVStack>
<Text :for={n <- 1..10000}>Item <%= n %></Text>
</LazyVStack>
</ScrollView>
"""
end
end
Spacers
Spacers take up all remaining space in a container.
Image originally from https://developer.apple.com/documentation/swiftui/spacer
Evaluate the following example and notice the Text
element is pushed to the right by the Spacer
.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<HStack>
<Spacer/>
<Text>This text is pushed to the right</Text>
</HStack>
"""
end
end
Your Turn: Bottom Text Spacer
In the cell below, use VStack
and Spacer
to place text in the bottom of the native view.
Example Solution
defmodule ServerWeb.ExampleLive.SwiftUI do
use ServerNative, [:render_component, format: :swiftui]
def render(assigns, _interface) do
~LVN"""
<VStack>
<Spacer/>
<Text>Hello</Text>
</VStack>
"""
end
end
Enter Your Solution Below
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<!-- Enter solution below -->
"""
end
end
AsyncImage
AsyncImage
is best for network images, or images served by the Phoenix server.
Here's an example of AsyncImage
with a lorem picsum image from https://picsum.photos/400/600.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<AsyncImage url="https://picsum.photos/400/400"/>
"""
end
end
Loading Spinner
AsyncImage
displays a loading spinner while loading the image. Here's an example of using AsyncImage
without a URL so that it loads forever.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<AsyncImage url=""></AsyncImage>
"""
end
end
Relative Path
For images served by the Phoenix server, LiveView Native evaluates URLs relative to the LiveView's host URL. This way you can use the path to static resources as you normally would in a Phoenix application.
For example, the path /images/logo.png
evaluates as http://localhost:4000/images/logo.png below. This serves the LiveView Native logo.
Evaluate the example below to see the LiveView Native logo in the iOS simulator.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<AsyncImage url="/images/logo.png"/>
"""
end
end
Image
The Image
element is best for system images such as the built in SF Symbols or images placed into the SwiftUI asset catalogue.
System Images
You can use the system-image
attribute to provide the name of system images to the Image
element.
For the full list of SF Symbols you can download Apple's Symbols 5 application.
Evaluate the cell below to see an example using the square.and.arrow.up
symbol.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<Image system-name="square.and.arrow.up" />
"""
end
end
Your Turn: Asset Catalogue
You can place assets in your SwiftUI application's asset catalogue. Using the asset catalogue for SwiftUI assets provide many benefits such as device-specific image variants, dark mode images, high contrast image mode, and improved performance.
Follow this guide: https://developer.apple.com/documentation/xcode/managing-assets-with-asset-catalogs#Add-a-new-asset to create a new asset called Image.
Then evaluate the following example and you should see this image in your simulator. For a convenient image, you can right-click and save the following LiveView Native logo.
You will need to rebuild the native application to pick up the changes to the assets catalogue.
Enter Your Solution Below
You should not need to make changes to this cell. Set up an image in your asset catalogue named "Image", rebuild your native application, then evaluate this cell. You should see the image in your iOS simulator.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<Image name="Image"/>
"""
end
end
Button
A Button is a clickable SwiftUI View.
The label of a button can be any view, such as a Text view for text-only buttons or a Label view for buttons with icons.
Evaluate the example below to see the SwiftUI Button element.
defmodule ServerWeb.ExampleLive.SwiftUI do
use LiveViewNative.Component,
format: :swiftui
def render(assigns, _interface) do
~LVN"""
<Button><Text>Text Button</Text></Button>
<Button><Label system-image="bolt.fill">Icon Button</Label></Button>
"""
end
end
Further Resources
See the SwiftUI Documentation for a complete list of SwiftUI elements and the LiveView Native SwiftUI Documentation for LiveView Native examples of the SwiftUI elements.