> For the complete documentation index, see [llms.txt](https://docs.firework.com/firework-for-developers/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.firework.com/firework-for-developers/ios-sdk/integration-guide-for-ios-sdk/storyblock.md).

# Story Block (iOS)

### Display Story Block

#### Use FWSStoryBlockView

The `FWSStoryBlockView` provides a `UIView` wrapper for the `StoryBlockViewController`. You can customize the `FWSStoryBlockView` just like the `StoryBlockViewController`.

**Integration**

1. Import `FireworkVideo`.
2. Instantiate `FWSStoryBlockView` and embed it.

The following are the sample codes:

<pre class="language-swift"><code class="lang-swift">import UIKit
import FireworkVideo

<strong>class ViewController: UIViewController {
</strong>    override func viewDidLoad() {
        super.viewDidLoad()
        self.addStoryBlockView()
    }

    func addStoryBlockView() {
        let storyBlockView = FWSStoryBlockView(source: .discover)
        // Please ensure that viewConfiguration and isPictureInPictureEnabled are set
        // before attaching it to the parent view
        storyBlockView.viewConfiguration = getStoryBlockConfiguration()
        storyBlockVC.isPictureInPictureEnabled = true

        storyBlockView.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(storyBlockView)

        NSLayoutConstraint.activate([
            storyBlockView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            storyBlockView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
            storyBlockView.heightAnchor.constraint(equalToConstant: 500),
            storyBlockView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
        ])
    }

    func getStoryBlockConfiguration() -> StoryBlockConfiguration {
        var viewConfiguration = StoryBlockConfiguration()
        viewConfiguration.playbackButton.isHidden = false
        viewConfiguration.fullScreenPlayerView.playbackButton.isHidden = false
        return viewConfiguration
    }
}
</code></pre>

{% hint style="warning" %}
Presently, it is necessary to configure the story block before it is attached to the parent view, due to a limitation in the current implementation.
{% endhint %}

#### Use FWSStoryBlockSwiftUIView (SwiftUI)

The `FWSStoryBlockSwiftUIView` provides a SwiftUI View wrapper for the `StoryBlockViewController`. You can customize the `FWSStoryBlockSwiftUIView` just like the `StoryBlockViewController`.

**Integration**

1. Import `FireworkVideo`.
2. Instantiate `FWSStoryBlockSwiftUIView` and embed it.

The following are the sample codes:

```swift
import SwiftUI
import FireworkVideo

struct ContentView: View {
    let storyBlockContainer = FWSStoryBlockSwiftUIContainer()
    var body: some View {
        List {
            Spacer()
            FWSStoryBlockSwiftUIView(
                source: .discover,
                viewConfiguration: getStoryBlockConfiguration(),
                isPictureInPictureEnabled: true,
                onStoryBlockLoaded: {
                    debugPrint("Story block loaded successfully.")
                },
                onStoryBlockFailedToLoad: { error in
                    debugPrint("Story block did fail loading.")
                },
                container: storyBlockContainer
            ).frame(height: 500)
            Button("Play") {
                storyBlockContainer.handler?.play()
            }
            Button("Pause") {
                storyBlockContainer.handler?.pause()
            }
            Spacer()
        }
    }

    func getStoryBlockConfiguration() -> StoryBlockConfiguration {
        var viewConfiguration = StoryBlockConfiguration()
        viewConfiguration.playbackButton.isHidden = false
        viewConfiguration.fullScreenPlayerView.playbackButton.isHidden = false
        return viewConfiguration
    }
}
```

### **Story Block Content Source**

The enum `StoryBlockContentSource` defines the different sources that can be used to populate the story block.

#### **Channel**

Displays content from the specified channel id.

```swift
let channelID = "<Channel ID>"
// UIKit
let storyBlockView = FWSStoryBlockView(source: .channel(channelID: channelID))
// SwiftUI
FWSStoryBlockSwiftUIView(source: .channel(channelID: channelID))
```

#### **Channel Playlist**

Displays content from the specified playlist id.

```swift
let channelID = "<Your Channel ID>"
let playlistID = "<Playlist ID>"
// UIKit
let storyBlockView = FWSStoryBlockView(source: .channelPlaylist(channelID: channelID, playlistID: playlistID))
// SwiftUI
FWSStoryBlockSwiftUIView(source: .channelPlaylist(channelID: channelID, playlistID: playlistID))
```

#### **Dynamic Content**

Displays dynamic content based on the provided channel id and content parameters.

```swift
let channelID = "<Channel ID>"
let parameters: DynamicContentParameters = ["<cohort key>": ["<cohort value 1>", "<cohort value 2>"]]
// UIKit
let storyBlockView = FWSStoryBlockView(source: .dynamicContent(channelID: channelID, parameters: parameters))
// SwiftUI
FWSStoryBlockSwiftUIView(source: .dynamicContent(channelID: channelID, parameters: parameters))
```

#### Hashtag Playlist

Displays content based on the provided channel id and the hashtag expression.

```swift
let channelID = "<Channel ID>"
let singleHashtag = "dogs"
// UIKit
let storyBlockView = FWSStoryBlockView(source: .hashtagPlaylist(channelID: channelID, filterExpression: singleHashtag))
// SwiftUI
FWSStoryBlockSwiftUIView(source: .hashtagPlaylist(channelID: channelID, filterExpression: singleHashtag))
```

Or a more advanced hashtag expression can be used to fine tune the results

```swift
let channelID = "<Channel ID>"
let filterExpression = "(and sport (or food comedy))"
// UIKit
let storyBlockView = FWSStoryBlockView(source: .hashtagPlaylist(channelID: channelID, filterExpression: filterExpression))
// SwiftUI
FWSStoryBlockSwiftUIView(source: .hashtagPlaylist(channelID: channelID, filterExpression: filterExpression))
```

#### SKU Playlist

Displays content based on the provided channel id and SKU/Product external IDs.

```swift
let channelID = "<Encoded Channel ID>"
let productIDs = ["<Product ID>", ...]
let storyBlockView = FWSStoryBlockView(source: .skuPlaylist(channelID: channelID, productIDs: productIDs))
FWSStoryBlockSwiftUIView(source: .skuPlaylist(channelID: channelID, productIDs: productIDs
```

#### Single Video or Live Stream

Displays a single video or live stream content.

```swift
let videoID = "<Video ID>"
// UIKit
let storyBlockView = FWSStoryBlockView(source: .singleContent(contentID: videoID))
// SwiftUI
FWSStoryBlockSwiftUIView(source: .singleContent(contentID: videoID))
```

#### Video Ads

Displays video ads based on vast XML.

```swift
// Specifies which channel the video ads should be created under.
let adChannelId = "<Channel ID>"
// The vast XML.
let vastXml = "<Vast XML>"
// UIKit
let storyBlockView = FWSStoryBlockView(source: .videoAds(adChannelId: adChannelId, vastXml: vastXml))
// SWiftUI
FWSStoryBlockSwiftUIView(source: .videoAds(adChannelId: adChannelId, vastXml: vastXml))
```

#### Channel Videos

```swift
let channelID = "Encoded channel id"
let videoIDs = ["Encoded video id 1", "Encoded video id 2", ...]
// UIKit
let storyBlockView = FWSStoryBlockView(source: .channelVideos(channelID: channelID, videoIDs: videoIDs))
// SwiftUI
FWSStoryBlockView(source: .channelVideos(channelID: channelID, videoIDs: videoIDs))
```

### Receive story block events

1. Set the delegate

```swift
storyBlockVC.delegate = self
```

2. Conform to `StoryBlockViewControllerDelegate` protocol

```swift
func storyBlockDidLoadFeed(
    _ viewController: StoryBlockViewController
) {
    debugPrint("Story block loaded successfully.")
}

func storyBlock(
    _ viewController: StoryBlockViewController,
    didFailToLoadFeed error: StoryBlockError
) {
    debugPrint("Story block did fail loading.")
    if case .contentSourceError(let feedContentSourceError) = error,
       case .emptyFeed = feedContentSourceError {
        // This is a specific error.
        // SDK will trigger this error when the story block is empty.
        // For example, host app can hide story block when receive this error.
    } else {
        // Other error
    }
}
```

### Play and pause StoryBlock programmatically

#### Use FWSStoryBlockView

```swift
let storyBlockView = FWSStoryBlockView(source: .discover)

// Play StoryBlock Programmatically
storyBlockView.play()

// Pause StoryBlock Programmatically
storyBlockView.pause()
```

#### Use FWSStoryBlockSwiftUIView

```swift
import SwiftUI
import FireworkVideo

struct ContentView: View {
    let storyBlockContainer = FWSStoryBlockSwiftUIContainer()
    var body: some View {
        List {
            Spacer()
            FWSStoryBlockSwiftUIView(
                source: .discover,
                container: storyBlockContainer
            ).frame(height: 500)
            Button("Play") {
                storyBlockContainer.handler?.play()
            }
            Button("Pause") {
                storyBlockContainer.handler?.pause()
            }
            Spacer()
        }
    }
}
```

### **Enter Story Block Fullscreen Programmatically**

#### Use FWSStoryBlockView

```swift
let storyBlockView = FWSStoryBlockView(source: .discover)

// Enter story block fullscreen programmatically
storyBlockView.enterFullScreen()
```

#### Use FWSStoryBlockSwiftUIView

```swift
import SwiftUI
import FireworkVideo

struct ContentView: View {
    let storyBlockContainer = FWSStoryBlockSwiftUIContainer()
    var body: some View {
        List {
            Spacer()
            FWSStoryBlockSwiftUIView(
                source: .discover,
                container: storyBlockContainer
            ).frame(height: 500)
            Button("Enter fullscreen") {
                // Enter story block fullscreen programmatically
                storyBlockContainer.handler?.enterFullScreen()
            }
            Spacer()
        }
    }
}
```

### **Autoplay**

Autoplay lets the story block automatically start playing without requiring user interaction. It is controlled by:

```swift
viewConfiguration.autoplay.isEnabled = true // default
```

This is the prerequisite for everything below — it is `true` by default, so autoplay is on out of the box. When and how playback is triggered then depends on whether viewport-based autoplay is turned on (`viewport.useSafeAreaViewport`).

#### Container-based autoplay (default)

When `viewport.useSafeAreaViewport = false` (the default), autoplay is tied to the component's on-screen presence:

* Autoplay starts when the component is at least partially on screen, and pauses when the component is fully off-screen.

#### Viewport-based autoplay

When the component is embedded in a `ScrollView`, `TableView`, or `CollectionView`, you can opt into viewport-based autoplay by setting `viewport.useSafeAreaViewport = true`. In this mode, visibility is measured against the viewport rather than the screen:

* Autoplay starts when the component's visibility within the viewport is greater than or equal to `viewport.threshold`, and pauses when it falls below.
* The viewport itself is customizable via `viewport.safeAreaEdges` and `viewport.additionalViewportExcludedInset` (see below).

```swift
func getStoryBlockConfiguration() -> StoryBlockConfiguration {
    var viewConfiguration = StoryBlockConfiguration()
    viewConfiguration.playbackButton.isHidden = false
    viewConfiguration.fullScreenPlayerView.playbackButton.isHidden = false

    // With the following configurations, the story block will autoplay when 50%
    // or more of its content is visible within the viewport,
    // and will cease autoplay when visibility falls below 50%.
    viewConfiguration.viewport.useSafeAreaViewport = true
    // Define visibility as 50% or more of the component being within the viewport.
    viewConfiguration.viewport.threshold = 0.5
    return viewConfiguration
}
```

#### Customize viewport

The default viewport is defined as the screen bounds minus the safe area insets—such as the status bar, top navigation bar, bottom tab bar, and bottom home indicator.

```swift
/// Default Viewport = Screen - Top Safe Area - Bottom Safe Area
/// 
/// Screen (Full Device Screen)
/// ┌─────────────────────────┐ ← Screen Top
/// │   Status Bar            │ ← Safe Area (excluded)
/// ├─────────────────────────┤
/// │   Navigation Bar        │ ← Safe Area (excluded, if present)
/// ├─────────────────────────┤
/// │                         │
/// │                         │
/// │   Default Viewport      │ ← Visible content area
/// │   (Visible Content)     │    (Screen - Safe Area)
/// │                         │
/// │                         │
/// ├─────────────────────────┤
/// │   Tab Bar               │ ← Safe Area (excluded, if present)
/// ├─────────────────────────┤
/// │   Home Indicator        │ ← Safe Area (excluded)
/// └─────────────────────────┘ ← Screen Bottom
```

However, you can further refine this viewport by specifying `safeAreaEdges` and `additionalViewportExcludedInset`. Please refer to the following code snippets for more details.

```swift
viewConfiguration.viewport.useSafeAreaViewport = true
/// Viewport Configuration:
/// 
/// With the configuration below, the viewport is calculated as:
/// Viewport = Screen - Top Safe Area - 50pt Bottom Padding
/// 
/// Visual Layout:
/// ┌─────────────────────────┐
/// │ Status Bar              │ \
/// ├─────────────────────────┤  > Excluded (safeAreaEdges = .top)
/// │ Nav Bar (if present)    │ /
/// ╞═════════════════════════╡ ← Viewport Top
/// │                         │
/// │   VIEWPORT AREA         │ ← Content visible here
/// │                         │
/// ╞═════════════════════════╡ ← Viewport Bottom
/// │ 50pt bottom padding     │ ← Excluded (additionalViewportExcludedInset.bottom)
/// └─────────────────────────┘
/// 
viewConfig.viewport.safeAreaEdges = .top
viewConfig.viewport.additionalViewportExcludedInset = UIEdgeInsets(
    top: 0,
    left: 0,
    bottom: 50,
    right: 0
)
```

### Player configurations

Please refer to [Player configurations (iOS)](/firework-for-developers/ios-sdk/integration-guide-for-ios-sdk/customization-ios/player-configurations-ios.md).

### Story block layout

You can customize the width and height of the story block to achieve either a horizontal or vertical layout.

#### Vertical layout

As shown in the following code snippets, we set the story block’s width to 350 and height to 533 to achieve a horizontal layout.

**Use StoryBlockViewController**

```swift
import FireworkVideo

class ViewController: UIViewController {

    func embedFeedInViewController() {
        let storyBlockVC = StoryBlockViewController(source: .discover)
        // Please ensure that viewConfiguration and isPictureInPictureEnabled are set
        // before attaching it to the parent view
        storyBlockVC.viewConfiguration = getStoryBlockConfiguration()
        storyBlockVC.isPictureInPictureEnabled = true

        self.addChild(storyBlockVC)

        storyBlockVC.view.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(storyBlockVC.view)
        let width = 350
        let height = 533
        NSLayoutConstraint.activate([
            storyBlockVC.view.widthAnchor.constraint(equalToConstant: width),
            storyBlockVC.view.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)
            storyBlockVC.view.heightAnchor.constraint(equalToConstant: height),
            storyBlockVC.view.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
        ])

        storyBlockVC.didMove(toParent: self)
    }

    func getStoryBlockConfiguration() -> StoryBlockConfiguration {
        var viewConfiguration = StoryBlockConfiguration()
        viewConfiguration.playbackButton.isHidden = false
        viewConfiguration.fullScreenPlayerView.playbackButton.isHidden = false
        return viewConfiguration
    }
}
```

**Use StoryBlockView**

```swift
import UIKit
import FireworkVideo
import FireworkVideoUI

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.addStoryBlockView()
    }

    func addStoryBlockView() {
        let storyBlockView = StoryBlockView(source: .discover)
        // Please ensure that viewConfiguration and isPictureInPictureEnabled are set
        // before attaching it to the parent view
        storyBlockView.viewConfiguration = getStoryBlockConfiguration()
        storyBlockView.isPictureInPictureEnabled = true

        storyBlockView.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(storyBlockView)

        let width = 350
        let height = 533
        NSLayoutConstraint.activate([
            storyBlockView.widthAnchor.constraint(equalToConstant: width),
            storyBlockView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)
            storyBlockView.heightAnchor.constraint(equalToConstant: height),
            storyBlockView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
        ])
    }

    func getStoryBlockConfiguration() -> StoryBlockConfiguration {
        var viewConfiguration = StoryBlockConfiguration()
        viewConfiguration.playbackButton.isHidden = false
        viewConfiguration.fullScreenPlayerView.playbackButton.isHidden = false
        return viewConfiguration
    }
}
```

**Use StoryBlockSwiftUIView**

```swift
import SwiftUI
import FireworkVideo
import FireworkVideoUI

struct ContentView: View {
    var body: some View {
        List {
            Spacer()
            StoryBlockSwiftUIView(
                source: .discover,
                viewConfiguration: getStoryBlockConfiguration(),
                isPictureInPictureEnabled: true,
                onStoryBlockLoaded: {
                    debugPrint("Story block loaded successfully.")
                },
                onStoryBlockFailedToLoad: { error in
                    debugPrint("Story block did fail loading.")
                }
            ).frame(width: 350, height: 533)
            Spacer()
        }
    }

    func getStoryBlockConfiguration() -> StoryBlockConfiguration {
        var viewConfiguration = StoryBlockConfiguration()
        viewConfiguration.playbackButton.isHidden = false
        viewConfiguration.fullScreenPlayerView.playbackButton.isHidden = false
        return viewConfiguration
    }
}
```

<figure><img src="/files/aaqwtlz06mkocwbjEpog" alt="" width="375"><figcaption></figcaption></figure>

#### Horizontal layout

As shown in the following code snippets, we set the story block’s width to 533 and height to 255 to achieve a horizontal layout.

**Use StoryBlockViewController**

```swift
import FireworkVideo

class ViewController: UIViewController {

    func embedFeedInViewController() {
        let storyBlockVC = StoryBlockViewController(source: .discover)
        // Please ensure that viewConfiguration and isPictureInPictureEnabled are set
        // before attaching it to the parent view
        storyBlockVC.viewConfiguration = getStoryBlockConfiguration()
        storyBlockVC.isPictureInPictureEnabled = true

        self.addChild(storyBlockVC)

        storyBlockVC.view.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(storyBlockVC.view)
        let width = 5330
        let height = 255
        NSLayoutConstraint.activate([
            storyBlockVC.view.widthAnchor.constraint(equalToConstant: width),
            storyBlockVC.view.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)
            storyBlockVC.view.heightAnchor.constraint(equalToConstant: height),
            storyBlockVC.view.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
        ])

        storyBlockVC.didMove(toParent: self)
    }

    func getStoryBlockConfiguration() -> StoryBlockConfiguration {
        var viewConfiguration = StoryBlockConfiguration()
        viewConfiguration.playbackButton.isHidden = false
        viewConfiguration.fullScreenPlayerView.playbackButton.isHidden = false
        return viewConfiguration
    }
}
```

**Use StoryBlockView**

```swift
import UIKit
import FireworkVideo
import FireworkVideoUI

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.addStoryBlockView()
    }

    func addStoryBlockView() {
        let storyBlockView = StoryBlockView(source: .discover)
        // Please ensure that viewConfiguration and isPictureInPictureEnabled are set
        // before attaching it to the parent view
        storyBlockView.viewConfiguration = getStoryBlockConfiguration()
        storyBlockView.isPictureInPictureEnabled = true

        storyBlockView.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(storyBlockView)

        let width = 533
        let height = 255
        NSLayoutConstraint.activate([
            storyBlockView.widthAnchor.constraint(equalToConstant: width),
            storyBlockView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)
            storyBlockView.heightAnchor.constraint(equalToConstant: height),
            storyBlockView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
        ])
    }

    func getStoryBlockConfiguration() -> StoryBlockConfiguration {
        var viewConfiguration = StoryBlockConfiguration()
        viewConfiguration.playbackButton.isHidden = false
        viewConfiguration.fullScreenPlayerView.playbackButton.isHidden = false
        return viewConfiguration
    }
}
```

**Use StoryBlockSwiftUIView**

```swift
import SwiftUI
import FireworkVideo
import FireworkVideoUI

struct ContentView: View {
    var body: some View {
        List {
            Spacer()
            StoryBlockSwiftUIView(
                source: .discover,
                viewConfiguration: getStoryBlockConfiguration(),
                isPictureInPictureEnabled: true,
                onStoryBlockLoaded: {
                    debugPrint("Story block loaded successfully.")
                },
                onStoryBlockFailedToLoad: { error in
                    debugPrint("Story block did fail loading.")
                }
            ).frame(width: 350, height: 533)
            Spacer()
        }
    }

    func getStoryBlockConfiguration() -> StoryBlockConfiguration {
        var viewConfiguration = StoryBlockConfiguration()
        viewConfiguration.playbackButton.isHidden = false
        viewConfiguration.fullScreenPlayerView.playbackButton.isHidden = false
        return viewConfiguration
    }
}
```

<figure><img src="/files/J2KdE5pz0lwtioLqKlrp" alt="" width="375"><figcaption></figcaption></figure>

### Loading indicator color

You can use the following code snippets to customize loading indicator color.

```
var configuration = StoryBlockConfiguration()
// Specifies the color of the loading indicator shown when content is loading
configuration.loadingIndicatorColor = .gray

let storyBlockVC = StoryBlockViewController(source: .discover)
storyBlockVC.viewConfiguration = configuration

let storyBlockView = StoryBlockView(source: .discover)
storyBlockView.viewConfiguration = configuration

StoryBlockSwiftUIView(
    source: .discover,
    viewConfiguration: configuration,
)
```
