Skip to main content

Shorts (SwiftUI)

This guide covers how to integrate Blue Billywig Shorts into your SwiftUI application using the BBNativePlayerKit-SwiftUI module.

Overview

Shorts are a vertical video player experience with swipe navigation, similar to TikTok, Instagram Reels, or YouTube Shorts. They provide an engaging, mobile-first way to present short-form video content.

important

Shorts use a dedicated BBNativeShorts view, which is separate from the standard BBNativePlayer. This is because Shorts require a specialized native view that supports vertical swipe navigation and the full Shorts playback experience.

Key features

  • Vertical video format — optimized for portrait orientation.
  • Swipe navigation — users swipe up/down to navigate between clips.
  • Auto-play — clips play automatically as users navigate.
  • Full-screen experience — designed for immersive viewing.
  • Native performance — wraps the native UIKit Shorts view from BBNativePlayerKit.
  • Reactive stateBBShortsState uses @Observable for per-property updates in SwiftUI.

Requirements

  • iOS 17.0+
  • Swift 5.9+
  • Xcode 15+
  • Both BlueBillywigNativePlayerKit-iOS and BBNativePlayerKit-SwiftUI products from bbnativeplayerkit-cocoapod. See the SwiftUI Getting Started for installation.

1. Minimal usage (auto-cleanup)

The convenience initializer creates an internal BBShortsState and destroys it automatically on .onDisappear. This is the simplest way to drop Shorts into a screen:

import SwiftUI
import BBNativePlayerKit_SwiftUI

struct ShortsScreen: View {
let shortsId: String

var body: some View {
BBNativeShorts(
jsonUrl: "https://demo.bbvms.com/sh/\(shortsId).json"
)
}
}

Use this form whenever you don't need to observe load/error state or call destroy() from elsewhere.


2. Full control with BBShortsState

When you want to react to load completion, surface errors, or trigger cleanup manually, pass an external BBShortsState. Call state.destroy() from .onDisappear to release SDK resources:

import SwiftUI
import BBNativePlayerKit_SwiftUI

struct ShortsScreen: View {
let shortsId: String
@State private var shortsState = BBShortsState()

var body: some View {
BBNativeShorts(
jsonUrl: "https://demo.bbvms.com/sh/\(shortsId).json",
options: BBShortsOptions(displayFormat: "full"),
state: shortsState
)
.onDisappear { shortsState.destroy() }
}
}
caution

If you pass an external state, the view does not auto-destroy it on disappear — you own the lifecycle. Forgetting shortsState.destroy() can leave audio playing in the background after the screen is dismissed.


3. Type-safe options

BBShortsOptions provides compile-time checked configuration. Use custom as an escape hatch for any options not yet covered by typed properties.

BBNativeShorts(
jsonUrl: "https://demo.bbvms.com/sh/58.json",
options: BBShortsOptions(
displayFormat: "full",
shelfStartSpacing: 16,
shelfEndSpacing: 16,
custom: [
"skipShortsAdOnSwipe": true
]
)
)

Available BBShortsOptions:

PropertyTypeDescription
displayFormatString?"full" (vertical swipe, default), "list" (horizontal shelf), or "player" (single player mode)
shelfStartSpacingInt?Padding at the start of the shelf scroll, in points. Only meaningful with displayFormat: "list".
shelfEndSpacingInt?Padding at the end of the shelf scroll, in points. Only meaningful with displayFormat: "list".
custom[String: Any]?Escape hatch for additional options (typed properties take precedence). Use for keys like skipShortsAdOnSwipe.

Display formats

FormatDescription
"full"Full-screen vertical swipe experience (default).
"list"Horizontal shelf / carousel — compact, scrollable thumbnails. Tapping a thumbnail opens the full-screen Shorts player as a modal.
"player"Single player mode.

4. Shelf mode ("list" format)

Shelf mode displays Shorts as a horizontal carousel, ideal for embedding within a feed alongside other content. Give the shelf a fixed height rather than letting it expand:

struct ShortsShelfRow: View {
let shortsId: String
@State private var shortsState = BBShortsState()

var body: some View {
BBNativeShorts(
jsonUrl: "https://demo.bbvms.com/sh/\(shortsId).json",
options: BBShortsOptions(
displayFormat: "list",
shelfStartSpacing: 16,
shelfEndSpacing: 16
),
state: shortsState
)
.frame(height: 400)
.clipShape(RoundedRectangle(cornerRadius: 16))
.onDisappear { shortsState.destroy() }
}
}

Key differences from full mode:

  • Horizontal scroll instead of vertical swipe.
  • Compact view — constrain with .frame(height:) instead of letting it fill the screen.
  • shelfStartSpacing / shelfEndSpacing control padding at the edges of the scroll.
  • Tapping a thumbnail opens the full-screen Shorts player as a modal overlay.

5. Observe state reactively

BBShortsState uses the @Observable macro, which provides per-property tracking. Views that read these properties update automatically when the SDK reports new values.

struct ShortsScreen: View {
let shortsId: String
@State private var shortsState = BBShortsState()

var body: some View {
ZStack {
Color.black.ignoresSafeArea()

BBNativeShorts(
jsonUrl: "https://demo.bbvms.com/sh/\(shortsId).json",
state: shortsState
)

VStack {
Spacer()
if let error = shortsState.error {
Text(error)
.foregroundColor(.red)
.padding(.bottom, 20)
} else if !shortsState.isReady {
Text("Loading Shorts…")
.foregroundColor(.white)
.padding(.bottom, 20)
}
}
}
.onDisappear { shortsState.destroy() }
}
}

Available state properties:

PropertyTypeDescription
isReadyBoolBecomes true when the Shorts JSON has been loaded and the view is set up.
errorString?Error message if setup failed. Reset to nil on destroy().

Method:

MethodDescription
destroy()Tear down the underlying BBNativeShortsView and release SDK resources. Safe to call multiple times.

6. Event hooks with .onChange

For one-off side effects on state changes, use .onChange modifiers. These bridge the bbNativeShortsView(_:didSetupWithJsonUrl:) and bbNativeShortsView(_:didFailWithError:) delegate methods on BBNativeShortsViewDelegate from the underlying UIKit SDK:

BBNativeShorts(
jsonUrl: "https://demo.bbvms.com/sh/58.json",
state: shortsState
)
.onChange(of: shortsState.isReady) { _, isReady in
if isReady { print("Shorts loaded") }
}
.onChange(of: shortsState.error) { _, error in
if let error { print("Shorts error: \(error)") }
}

7. URL format

Shorts URLs follow this pattern:

https://{domain}.bbvms.com/sh/{shortsId}.json

For example:

  • https://demo.bbvms.com/sh/58.json
  • https://your-publication.bbvms.com/sh/71.json
note

The jsonUrl is only read when the view is first created. To switch to a different Shorts configuration, dismiss the view and present a new one, or apply .id(jsonUrl) to force recreation.


8. Complete example

A full Shorts screen with status overlay and proper cleanup:

import SwiftUI
import BBNativePlayerKit_SwiftUI

struct ShortsScreen: View {
let shortsId: String
@State private var shortsState = BBShortsState()

var body: some View {
ZStack {
Color.black.ignoresSafeArea()

BBNativeShorts(
jsonUrl: "https://demo.bbvms.com/sh/\(shortsId).json",
options: BBShortsOptions(displayFormat: "full"),
state: shortsState
)
.ignoresSafeArea()

VStack {
Spacer()
if let error = shortsState.error {
Text(error)
.font(.footnote)
.foregroundColor(Color(red: 1.0, green: 0.42, blue: 0.42))
.padding(.bottom, 32)
} else if !shortsState.isReady {
ProgressView()
.tint(.white)
.padding(.bottom, 32)
}
}
}
.onDisappear { shortsState.destroy() }
}
}

9. BBNativeShorts vs BBNativePlayer

FeatureBBNativeShortsBBNativePlayer
Vertical swipe navigationYesNo
Full-screen Shorts experienceYesNo
Shelf / carousel layoutYes (displayFormat: "list")No
Standard video playbackNoYes
Programmatic playback controlNot exposed (handled by the SDK)Yes (via BBPlayerState)
State holderBBShortsStateBBPlayerState
Options typeBBShortsOptionsBBPlayerOptions
caution

Do not try to load Shorts URLs (.../sh/{id}.json) in BBNativePlayer. The regular player view does not support Shorts-specific features like swipe navigation.


10. Lifecycle and cleanup

Always release the Shorts view when leaving the screen to stop playback and free native resources. Without cleanup, audio may continue playing in the background.

There are two patterns:

Automatic (convenience initializer)

When you use init(jsonUrl:options:) without passing an external state, the view manages its own BBShortsState and destroys it on .onDisappear:

BBNativeShorts(
jsonUrl: "https://demo.bbvms.com/sh/58.json"
)
// No manual cleanup needed

Manual (external state)

When you pass your own BBShortsState — required if you want to observe isReady / error — you are responsible for cleanup:

@State private var shortsState = BBShortsState()

var body: some View {
BBNativeShorts(
jsonUrl: "https://demo.bbvms.com/sh/58.json",
state: shortsState
)
.onDisappear { shortsState.destroy() }
}

This ensures:

  • Video / audio playback stops when leaving the screen.
  • Native BBNativeShortsView resources are released.
  • No background audio continues after navigation.

11. Layout considerations

Shorts are designed for portrait viewing. SwiftUI layout works as expected — the view fills whatever frame you give it.

Full-screen vertical

BBNativeShorts(jsonUrl: "https://demo.bbvms.com/sh/58.json")
.ignoresSafeArea()

Within safe area

BBNativeShorts(jsonUrl: "https://demo.bbvms.com/sh/58.json")
// Default behavior — respects safe area insets

Embedded shelf

BBNativeShorts(
jsonUrl: "https://demo.bbvms.com/sh/58.json",
options: BBShortsOptions(displayFormat: "list")
)
.frame(height: 400)
.clipShape(RoundedRectangle(cornerRadius: 16))

12. API reference

BBNativeShorts

InitializerDescription
init(jsonUrl:options:)Self-managing view with internal state that auto-destroys on disappear. Use when you don't need to observe state.
init(jsonUrl:options:state:)External-state view for full control. Caller owns the BBShortsState lifecycle and must call state.destroy() on disappear.

BBShortsOptions

See Section 3 above.

BBShortsState

See Section 5 above.


13. Troubleshooting

Shorts not loading

  1. Verify the Shorts ID exists in your publication.
  2. Check the JSON URL format: https://{domain}.bbvms.com/sh/{shortsId}.json.
  3. Ensure your publication has Shorts enabled.
  4. Observe shortsState.error — the SDK reports setup failures here.

Swipe not working

  • Confirm you are using BBNativeShorts, not BBNativePlayer.
  • Check that the Shorts configuration contains multiple clips.
  • Verify displayFormat is "full" (or omitted — "full" is the default).

Black screen

  • Check network connectivity.
  • Verify the Shorts configuration contains valid video content.
  • Inspect Xcode console output for SDK error messages.
  • Confirm the hosting frame is non-zero. The Shorts view defers creation until viewDidLayoutSubviews reports non-zero bounds; if the SwiftUI parent collapses the frame, the view never initializes.

Audio continues after leaving the screen

This is the most common cleanup bug. Pick one of:

  1. Switch to the convenience initializer BBNativeShorts(jsonUrl:options:) if you don't need to observe state.
  2. Add .onDisappear { shortsState.destroy() } to the view that hosts BBNativeShorts.

Assertion: jsonUrl changed after creation

In DEBUG builds, the SwiftUI wrapper asserts that jsonUrl is stable for the lifetime of the view. To switch Shorts content, force view recreation by applying .id(jsonUrl) to the parent, or pop and push a new screen.