One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

iOS Media and Empty States

SwiftUI Image, shapes, VideoPlayer, LivePhotoView, WebView, Map, and empty states under One.iOS

One.iOS shapes, SF Symbols in several rendering modes, and a ContentUnavailableView

One.iOS media, shapes, and empty states are SwiftUI’s own: Image and AsyncImage, the shape views, VideoPlayer, LivePhotoView, WebView, Map with its MapKit controls, ContentUnavailableView, and EmptyView. Shapes fill the box React Native gives them; players and the web view take commands with revisions; the map and empty states render their content natively.

Reach for these when media should be drawn by the system: symbols, shapes, video with transport controls, a Live Photo, embedded web content, or a map. For bundled, remote, and cross-platform images use One.UI.Image instead, and for a cross-platform map with polylines, polygons, and circles use One.UI.Map. Every view here throws on web and Android.

Usage

import { One } from 'one'
function Status() {
return (
<One.iOS.VStack spacing={8}>
<One.iOS.Image systemName="wifi" swiftStyle={{ imageScale: 'large' }} />
<One.iOS.RoundedRectangle fill="blue" cornerRadius={12} style={{ height: 64 }} />
</One.iOS.VStack>
)
}

The symbol draws at large scale and the shape fills the width Yoga offers at a fixed height.

Every view also accepts accessibilityLabel, accessibilityHint, accessibilityValue, testID, style, onLayout, and swiftStyle.

How it works

Media views render through Fabric views hosting their SwiftUI content, like any control. Symbols and shapes measure from SwiftUI; players, the web view, and the map fill the box they are given, so give them an explicit size or flex. Commands run through a command string plus a revision: set the command and increment the revision for each run, including repeats, and native acknowledges each revision once.

Video keeps its position when other props change and creates a new player only when url changes. The Live Photo loader reads local Photos data and does not download an iCloud original. Web content loads once per url or html value, while the bridge script and navigation policy are configuration set at creation.

Image

Image draws an SF Symbol by name, or a remote image from uri.

import { One } from 'one'
function Weather() {
return (
<>
<One.iOS.Image
systemName="cloud.sun.rain.fill"
swiftStyle={{ symbolRenderingMode: 'multicolor', imageScale: 'large' }}
/>
<One.iOS.Image systemName="wifi" variableValue={0.5} />
</>
)
}
PropsTypeDescription
systemNameSFSymbolName | "" = ""The SF Symbol to draw.
SFSymbolName | "" = ""The SF Symbol to draw.
uristring = ""A remote image to draw in place of a symbol. It fills its frame.
string = ""A remote image to draw in place of a symbol. It fills its frame.
renderingMode"original" | "template" = "original"template lets a toolbar tint a remote image.
"original" | "template" = "original"template lets a toolbar tint a remote image.
variableValuenumberFrom 0 to 1, for symbols that fill in steps.
numberFrom 0 to 1, for symbols that fill in steps.
resizableboolean = falseScales a symbol to its frame instead of its point size.
boolean = falseScales a symbol to its frame instead of its point size.

symbolRenderingMode, symbolVariant, and imageScale are SwiftUI modifiers set through swiftStyle. Size a symbol with swiftStyle={{ fontSize }} and give the view a matching width and height, or React Native lays it out smaller than the glyph and neighbors overlap it.

AsyncImage

AsyncImage is SwiftUI’s AsyncImage. It shows a placeholder while it loads the image at url, then draws the image at its own size. Pass url="" to use the SDK nil URL and keep its default placeholder.

import { One } from 'one'
function Logo() {
return <One.iOS.AsyncImage url="https://reactnative.dev/img/tiny_logo.png" scale={2} />
}
PropsTypeDescription
url (required)stringThe image URL. "" shows only the placeholder.
stringThe image URL. "" shows only the placeholder.
scalenumber = 1Image pixels per point.
number = 1Image pixels per point.

On iOS 27, changing only scale or setting url to nil retains the loaded image under its current identity, matching SwiftUI. Change the React key to recreate the image with a new scale or show the nil URL placeholder.

This binding exposes init(url:scale:). The SDK’s image-content, placeholder, and phase builders, and the iOS 27 request initializers, are not bound: their callbacks return generic SwiftUI view values or take URLRequest, which the generated constructor bridge cannot carry.

Shapes

Circle, Capsule, Rectangle, and Ellipse fill a SwiftUI shape with fill, a color value, plus an optional strokeBorder drawn inside the edge over the fill, lineWidth points wide. A shape fills the box React Native gives it; widths must be zero or more.

import { One } from 'one'
function Badges() {
return (
<>
<One.iOS.Circle fill="red" style={{ width: 32, height: 32 }} />
<One.iOS.Capsule fill="white" strokeBorder="gray" lineWidth={2} style={{ width: 64, height: 32 }} />
</>
)
}
Circle, Capsule, Rectangle, EllipseTypeDescription
fillColorValueThe fill color. Every shape takes it.
ColorValueThe fill color. Every shape takes it.
strokeBorderColorValueA border color drawn inside the edge.
ColorValueA border color drawn inside the edge.
lineWidthnumber = 1The border width in points.
number = 1The border width in points.
RoundedRectangleTypeDescription
fillColorValueThe fill color. Every shape takes it.
ColorValueThe fill color. Every shape takes it.
strokeBorderColorValueA border color drawn inside the edge.
ColorValueA border color drawn inside the edge.
lineWidthnumber = 1The border width in points.
number = 1The border width in points.
cornerRadiusnumber = 0The corner radius in points.
number = 0The corner radius in points.
UnevenRoundedRectangleTypeDescription
fillColorValueThe fill color.
ColorValueThe fill color.
strokeBorderColorValueA border color drawn inside the edge.
ColorValueA border color drawn inside the edge.
lineWidthnumber = 1The border width in points.
number = 1The border width in points.
topLeadingRadiusnumber = 0The top leading corner.
number = 0The top leading corner.
bottomLeadingRadiusnumber = 0The bottom leading corner.
number = 0The bottom leading corner.
bottomTrailingRadiusnumber = 0The bottom trailing corner.
number = 0The bottom trailing corner.
topTrailingRadiusnumber = 0The top trailing corner.
number = 0The top trailing corner.

ConcentricRectangle needs iOS 26 and takes its corners from the enclosing container shape; with none, its corners can be square. It takes no border.

import { One } from 'one'
function Panels() {
return (
<>
<One.iOS.Ellipse fill="blue" style={{ width: 64, height: 32 }} />
<One.iOS.RoundedRectangle fill="blue" cornerRadius={12} style={{ width: 120, height: 64 }} />
<One.iOS.UnevenRoundedRectangle fill="green" topLeadingRadius={16} bottomTrailingRadius={16} style={{ width: 120, height: 64 }} />
<One.iOS.ConcentricRectangle fill="orange" style={{ width: 120, height: 80 }} />
</>
)
}
ConcentricRectangleTypeDescription
fillColorValueThe fill color.
ColorValueThe fill color.

VideoPlayer

VideoPlayer plays a local file:// or remote video from url with AVKit’s native transport controls. autoplay starts playback when a URL loads. The player keeps its position when other props change and creates a new player only when url changes.

import { One } from 'one'
function Trailer() {
return <One.iOS.VideoPlayer url="https://example.com/trailer.mp4" autoplay style={{ height: 220 }} />
}
PropsTypeDescription
urlstring = ""A file:// or remote video. A new url makes a new player.
string = ""A file:// or remote video. A new url makes a new player.
autoplayboolean = falsePlays once the video loads.
boolean = falsePlays once the video loads.
command"play" | "pause" | "seek" | "" = ""A command to run. See below.
"play" | "pause" | "seek" | "" = ""A command to run. See below.
commandRevisionnumber = 0Increase to run command again.
number = 0Increase to run command again.
seekToMsnumber = 0Where a seek goes, in milliseconds.
number = 0Where a seek goes, in milliseconds.
onPlaybackStatus(state: string, positionMs: number, durationMs: number) => voidloading, playing, paused, ended, or failed, with the time.
(state: string, positionMs: number, durationMs: number) => voidloading, playing, paused, ended, or failed, with the time.

Set command to play, pause, or seek and increment commandRevision for each command, including repeated commands of the same kind. Anything else throws, as does a command with a zero revision or a revision with no command. For seek, pass a nonnegative seekToMs. onPlaybackStatus(state, positionMs, durationMs) reports loading, playing, paused, ended, or failed with the current time and duration in milliseconds; unknown or indefinite duration reports as 0. Status arrives on state changes, after commands, and about twice per second while playing. Native transport taps also change the reported status.

import { useState } from 'react'
import { Button, View } from 'react-native'
import { One } from 'one'
function ControlledVideo() {
const [control, setControl] = useState({ command: '' as '' | 'play' | 'pause' | 'seek', revision: 0, seekToMs: 0 })
const send = (command: 'play' | 'pause' | 'seek', seekToMs = 0) =>
setControl((previous) => ({ command, seekToMs, revision: previous.revision + 1 }))
return (
<View>
<One.iOS.VideoPlayer
url="https://example.com/trailer.mp4"
style={{ height: 220 }}
command={control.command}
commandRevision={control.revision}
seekToMs={control.seekToMs}
onPlaybackStatus={(state, positionMs, durationMs) => console.log(state, positionMs, durationMs)}
/>
<Button title="Play" onPress={() => send('play')} />
<Button title="Pause" onPress={() => send('pause')} />
<Button title="Skip to 10s" onPress={() => send('seek', 10_000)} />
</View>
)
}

The player follows the app’s audio session. This view does not publish Now Playing metadata or remote transport callbacks. One.Audio manages a separate player with its own remote commands.

LivePhotoView

LivePhotoView displays and plays a Live Photo already in the Photos library. Request read access with One.PhotoLibrary.requestReadPermission(), then pass the identifier of an asset whose isLivePhoto is true. Give the view an explicit size. It loads a nondegraded PHLivePhoto and shows its key photo at ready. It does not capture or assemble a still and video pair.

import { useState } from 'react'
import { Button, View } from 'react-native'
import { One } from 'one'
function LivePhoto({ assetIdentifier }: { assetIdentifier: string }) {
const [control, setControl] = useState({ command: '' as '' | 'play' | 'stop', revision: 0 })
const send = (command: 'play' | 'stop') =>
setControl((previous) => ({ command, revision: previous.revision + 1 }))
return (
<View>
<One.iOS.LivePhotoView
assetIdentifier={assetIdentifier}
style={{ width: 320, height: 240 }}
command={control.command}
commandRevision={control.revision}
onPlaybackState={(state, errorCode) => console.log(state, errorCode)}
/>
<Button title="Play" onPress={() => send('play')} />
<Button title="Stop" onPress={() => send('stop')} />
</View>
)
}
PropsTypeDescription
assetIdentifier (required)stringThe Photos local identifier of a Live Photo.
stringThe Photos local identifier of a Live Photo.
autoplayboolean = falsePlays once the Live Photo is ready.
boolean = falsePlays once the Live Photo is ready.
command"play" | "stop" | "" = ""A command to run.
"play" | "stop" | "" = ""A command to run.
commandRevisionnumber = 0Increase to run command again.
number = 0Increase to run command again.
onPlaybackState(state: string, errorCode: string) => voidloading, ready, playing, ended, or failed.
(state: string, errorCode: string) => voidloading, ready, playing, ended, or failed.

autoplay starts when the nondegraded Live Photo reaches ready. Otherwise, send play or stop with an increasing positive commandRevision, including for repeated commands. The event order for a normal full play is loading, ready, playing, ended. stop returns to ready and shows the key photo. onPlaybackState receives (state, errorCode); errorCode is '' unless state is failed.

Failures report E_LIVE_PHOTO_INPUT for a blank identifier, E_LIVE_PHOTO_PERMISSION without Photos read access, E_LIVE_PHOTO_NOT_FOUND for an unknown or inaccessible asset, E_LIVE_PHOTO_NOT_LIVE for an ordinary image, or E_LIVE_PHOTO_LOAD when Photos cannot supply the Live Photo. The JavaScript wrapper rejects a blank identifier before mounting. The loader uses local Photos data and does not download an iCloud original, so a cloud-only asset may fail with E_LIVE_PHOTO_LOAD.

WebView

WebView embeds web content from url or an html string, exactly one of the two. Give the view an explicit height because it fills its React Native box.

import { useState } from 'react'
import { One } from 'one'
function Help({ url }: { url: string }) {
const [revision, setRevision] = useState(0)
return (
<>
<One.iOS.Button label="Reload" onPress={() => setRevision((r) => r + 1)} />
<One.iOS.WebView
url={url}
command={revision > 0 ? 'reload' : ''}
commandRevision={revision}
style={{ height: 400 }}
/>
</>
)
}
PropsTypeDescription
urlstring = ""The page to load.
string = ""The page to load.
htmlstring = ""Markup to load in place of a url.
string = ""Markup to load in place of a url.
scriptstring = ""JavaScript to insert at document start.
string = ""JavaScript to insert at document start.
command"reload" | "goBack" | "goForward" | "evaluate" | "postMessage" | "" = ""A command to run. See below.
"reload" | "goBack" | "goForward" | "evaluate" | "postMessage" | "" = ""A command to run. See below.
commandRevisionnumber = 0Increase to run command again.
number = 0Increase to run command again.
commandValuestring = ""The script or message for evaluate and postMessage.
string = ""The script or message for evaluate and postMessage.
limitsNavigationsToAppBoundDomainsboolean = falseOnly allow app-bound domains.
boolean = falseOnly allow app-bound domains.
inlineMediaboolean = falsePlay page media inline.
boolean = falsePlay page media inline.
inspectableboolean = falseExpose the page to Safari devtools.
boolean = falseExpose the page to Safari devtools.
bouncesboolean = trueRubber-band past the content edge.
boolean = trueRubber-band past the content edge.
onNavigate(url: string) => voidThe page URL changed.
(url: string) => voidThe page URL changed.
onTitleChange(title: string) => voidThe page title changed.
(title: string) => voidThe page title changed.
onLoadingChange(loading: boolean, progress: number) => voidLoading started, progressed, or ended.
(loading: boolean, progress: number) => voidLoading started, progressed, or ended.
onLoadStart() => voidA navigation started.
() => voidA navigation started.
onLoadEnd() => voidA navigation finished.
() => voidA navigation finished.
onError(message: string) => voidA load failed.
(message: string) => voidA load failed.
onHttpError(statusCode: number) => voidA response carried an HTTP error status.
(statusCode: number) => voidA response carried an HTTP error status.
onMessage(data: string) => voidThe page posted a bridge message.
(data: string) => voidThe page posted a bridge message.
onHistoryChange(canGoBack: boolean, canGoForward: boolean) => voidBack-forward availability changed.
(canGoBack: boolean, canGoForward: boolean) => voidBack-forward availability changed.
onProcessTerminate() => voidThe web content process ended.
() => voidThe web content process ended.
backForwardNavigationGesturesBackForwardNavigationGesturesBehavior | "" = ""Edge swipes to go back and forward.
BackForwardNavigationGesturesBehavior | "" = ""Edge swipes to go back and forward.
magnificationGesturesMagnificationGesturesBehavior | "" = ""Pinch to zoom.
MagnificationGesturesBehavior | "" = ""Pinch to zoom.
linkPreviewsLinkPreviewBehavior | "" = ""Long press link previews.
LinkPreviewBehavior | "" = ""Long press link previews.
elementFullscreenElementFullscreenBehavior | "" = ""Lets page video go full screen.
ElementFullscreenBehavior | "" = ""Lets page video go full screen.
contentBackgroundVisibility | "" = ""The web view background.
Visibility | "" = ""The web view background.

The gesture and behavior props take automatic, enabled, or disabled, and "" leaves them unset. Use html for markup already in the app: updating that string loads the new document and reports its title through onTitleChange.

Set command to reload, goBack, goForward, evaluate, or postMessage and increment commandRevision for each command, including repeated commands of the same kind. A command with a zero revision, or a revision with no command, throws; evaluate and postMessage need a commandValue. evaluate runs commandValue as JavaScript; postMessage dispatches a message event whose data is commandValue. The page calls window.ReactNativeWebView.postMessage to reach onMessage. script runs at document start but rebuilds the page when it changes, so it is for fixed page setup rather than repeated calls.

PhotosPicker

One.iOS.PhotosPicker is documented with the other pickers in iOS Pickers.

Map

Map is the SwiftUI map. Latitude must sit between -90 and 90, longitude between -180 and 180, and distance above zero, or the map throws.

import { One } from 'one'
function Office({ pins }: { pins: { id: string; label: string; latitude: number; longitude: number }[] }) {
return (
<One.iOS.Map
latitude={37.7749}
longitude={-122.4194}
distance={2000}
markers={pins}
onRegionChange={(latitude, longitude, distance) => console.log(latitude, longitude, distance)}
style={{ flex: 1 }}
/>
)
}
PropsTypeDescription
latitudenumber = 37.7749The region center.
number = 37.7749The region center.
longitudenumber = -122.4194The region center.
number = -122.4194The region center.
distancenumber = 5000How far the camera sits from the center, in meters.
number = 5000How far the camera sits from the center, in meters.
markers (required)readonly MapMarker[]Pins, each { id, label, latitude, longitude }.
readonly MapMarker[]Pins, each { id, label, latitude, longitude }.
onRegionChange(latitude: number, longitude: number, distance: number) => voidThe region when a move ends.
(latitude: number, longitude: number, distance: number) => voidThe region when a move ends.

Map controls

One.iOS.MapCompass, One.iOS.MapPitchToggle, and One.iOS.MapUserLocationButton are the MapKit controls. Nest them under Map and they render in the map’s own controls, operating on the owning map. Any other child throws instead of mounting.

import { One } from 'one'
function MapWithControls({ pins }: { pins: { id: string; label: string; latitude: number; longitude: number }[] }) {
return (
<One.iOS.Map markers={pins} style={{ flex: 1 }}>
<One.iOS.MapCompass />
<One.iOS.MapPitchToggle />
</One.iOS.Map>
)
}
Map childrenTypeDescription
childrenReactNodeMap controls: MapCompass, MapPitchToggle, MapUserLocationButton.
ReactNodeMap controls: MapCompass, MapPitchToggle, MapUserLocationButton.

ContentUnavailableView

ContentUnavailableView renders an empty state with action buttons. Give it a height or flex: 1; it fills the area React Native assigns. A React state update after an action can change the view or replace it.

import { One } from 'one'
function NoResults({ reload }: { reload: () => void }) {
return (
<One.iOS.ContentUnavailableView
title="No results"
systemImage="magnifyingglass"
description="Try a different search."
actions={[{ id: 'retry', label: 'Retry' }]}
onAction={(id) => {
if (id === 'retry') reload()
}}
/>
)
}
PropsTypeDescription
titlestring = ""The headline.
string = ""The headline.
systemImageSFSymbolName | "" = ""An SF Symbol above the title.
SFSymbolName | "" = ""An SF Symbol above the title.
descriptionstring = ""Text under the title.
string = ""Text under the title.
actions (required)readonly DialogAction[]Buttons, each { id, label, role }.
readonly DialogAction[]Buttons, each { id, label, role }.
onAction(id: string) => voidA tapped action.
(id: string) => voidA tapped action.

EmptyView

EmptyView renders nothing. It is a placeholder where SwiftUI expects a view, with no props beyond the shared view props. Inside a SwiftUI HStack, it adds no visible element or extra spacing. It has zero visible height. In a React Native container with gap, the gaps on either side still apply to its position in the child list.

import { One } from 'one'
function MaybeBadge({ count }: { count: number }) {
return (
<One.iOS.HStack>
<One.iOS.Text text="Inbox" />
{count > 0 ? <One.iOS.Text text={`${count}`} /> : <One.iOS.EmptyView />}
</One.iOS.HStack>
)
}

Platform differences

iOSAndroidWeb
RendersSwiftUI mediaThrowsThrows
VideoAVKit playerNoneNone
WebWKWebViewNoneNone
MapSwiftUI mapNoneNone

Limits and gotchas

Edit this page on GitHub.