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

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