# Circle Story (iOS)

### Use CircleStoryView

```swift
import UIKit
import FireworkVideo

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

    func addVideoFeedView() {
        let channelID = "<Encoded Channel ID>"
        let playlistID = "<Encoded Playlist ID>"
        let source = VideoFeedContentSource.channelPlaylist(
            channelID: channelID,
            playlistID: playlistID
        )
        let circleStoryView = CircleStoryView(source: source)
        circleStoryView.isPictureInPictureEnabled = true

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

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

```

### Use CircleStorySwiftUIView

```swift
import SwiftUI
import FireworkVideo

let channelID = "<Encoded Channel ID>"
let playlistID = "<Encoded Playlist ID>"

struct ContentView: View {
    var body: some View {
        List {
            Spacer()
            CircleStorySwiftUIView(
                source: .channelPlaylist(channelID: channelID, playlistID: playlistID),
                isPictureInPictureEnabled: true,
                onCircleStoryLoaded: {
                    debugPrint("Circle story loaded successfully.")
                },
                onCircleStoryFailedToLoad: { error in
                    debugPrint("Circle story did fail loading.")
                }
            ).frame(height: 240)
            Button("Refresh") {
                videoFeedContainer.handler?.refresh()
            }
            Spacer()
        }
    }
}

```

### **Content Source**

Please refer to [Video Feed Content Source (iOS)](/firework-for-developers/ios-sdk/integration-guide-for-ios-sdk/video-feed-content-source-ios.md).

### **Autoplay**

Autoplay lets the circle story automatically start playing the first eligible item without requiring user interaction. To enable it, set:

```swift
viewConfiguration.itemView.autoplay.isEnabled = true
```

All behavior described below assumes this prerequisite is met. Which item is picked as "eligible" depends on the visibility threshold (`viewConfiguration.itemView.autoplay.triggerVisibilityPercentage`) and on whether viewport-based autoplay is turned on (`useSafeAreaViewport`).

#### Container-based autoplay (default)

When `useSafeAreaViewport = false` (the default), visibility is measured against the circle story component's own bounds:

* Autoplay is active whenever the component is at least partially on screen, and inactive when the component is fully off-screen.
* While active, the first item whose visibility within the component's bounds is greater than or equal to `triggerVisibilityPercentage` starts playing automatically.

#### Viewport-based autoplay

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

* The first item whose visibility **within the viewport** is greater than or equal to `triggerVisibilityPercentage` starts playing automatically.
* The viewport itself is customizable via `safeAreaEdges` and `additionalViewportExcludedInset` (see below).

```swift
func getCircleStoryContentConfiguration() -> CircleStoryContentConfiguration {
    var viewConfiguration = CircleStoryContentConfiguration()
    // Enable viewport-based autoplay
    viewConfiguration.useSafeAreaViewport = true
    viewConfiguration.itemView.autoplay.isEnabled = true
    viewConfiguration.playerView.playbackButton.isHidden = false
    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.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.safeAreaEdges = .top
viewConfig.additionalViewportExcludedInset = UIEdgeInsets(
    top: 0,
    left: 0,
    bottom: 50,
    right: 0
)
```


---

# 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/circle-story-ios.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.
