# 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()
        }
    }
}
```

### **Viewport-based autoplay support**

By default, autoplay is enabled, and the video begins playing as soon as the component is instantiated. However, when the component is embedded within a `ScrollView`, `TableView`, or `CollectionView`, a more seamless user experience is achieved by initiating autoplay only when the component enters the viewport, and pausing playback when it leaves. This behavior can be implemented setting `useSafeAreaViewport` as `true`.

```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,
)
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.firework.com/firework-for-developers/ios-sdk/integration-guide-for-ios-sdk/storyblock.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
