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

#### Viewport-based autoplay (default)

Autoplay is viewport-based by default, which delivers a seamless experience when the component is embedded in a `ScrollView`, `TableView`, or `CollectionView`. 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.
* When the circle story is embedded in your own scroll container, the viewport is clipped to that container automatically — a circle story scrolled out of it correctly pauses, with no extra configuration.
* The viewport itself is customizable via `safeAreaEdges` (see below).

```swift
func getCircleStoryContentConfiguration() -> CircleStoryContentConfiguration {
    var viewConfiguration = CircleStoryContentConfiguration()
    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
```

The viewport is then clipped to whatever your own layout hides. The SDK walks up from the circle story and intersects the bounds of every ancestor view that clips its content, so a circle story scrolled out of your scroll container — or hidden behind a header stacked above that container — is measured as not visible. This is automatic and needs no configuration.

```swift
/// Circle story embedded in your own scroll view:
///
/// ┌─ Screen ────────────────────────┐
/// │ Status Bar / Nav Bar            │ ← Safe Area (excluded)
/// ├─────────────────────────────────┤
/// │ Your page header                │ ← Excluded: outside the scroll
/// ╞═ Your scroll view (clips) ══════╡    view, which clips it away
/// │                                 │
/// │   Viewport                      │ ← What's left
/// │                                 │
/// ╞═════════════════════════════════╡
/// │ Rest of your page               │ ← Excluded, same as above
/// ├─────────────────────────────────┤
/// │ Home Indicator                  │ ← Safe Area (excluded)
/// └─────────────────────────────────┘
```

If you want only some safe-area edges excluded, narrow them with `safeAreaEdges`.

```swift
/// With the configuration below, only the top safe area is excluded:
/// Viewport = Screen - Top Safe Area
///
/// ┌─────────────────────────────────┐
/// │ Status Bar                      │ \
/// ├─────────────────────────────────┤  > Excluded (safeAreaEdges = .top)
/// │ Nav Bar (if present)            │ /
/// ╞═════════════════════════════════╡ ← Viewport Top
/// │                                 │
/// │   Viewport Area                 │ ← Content visible here
/// │                                 │
/// │                                 │
/// └─────────────────────────────────┘ ← Viewport Bottom (bottom safe area kept)
///
viewConfiguration.safeAreaEdges = .top
```
