Skip to main content

SwiftUI Integration

This guide covers integrating StorifyMe into SwiftUI applications using the native StorifyMeWidgetView wrapper.

Requirements
  • iOS 14.0 or later
  • StorifyMe SDK 2.6.0 or later

Quick Start

Basic Usage

import SwiftUI
import StorifyMe

struct ContentView: View {
var body: some View {
StorifyMeWidgetView(widgetId: 54)
}
}

With Dynamic Height

import SwiftUI
import StorifyMe

struct ContentView: View {
@State private var widgetHeight: CGFloat = 150

var body: some View {
ScrollView {
StorifyMeWidgetView(widgetId: 54, height: $widgetHeight)
.frame(height: widgetHeight)
}
}
}

With Event Callbacks

StorifyMeWidgetView(widgetId: 54)
.onStoriesLoaded { widgetId, stories in
print("Loaded \(stories.count) stories for widget \(widgetId)")
}
.onError { error in
print("Error: \(error.message)")
}
.onStoryOpened { story, index in
print("Opened story at index \(index)")
}

StorifyMeWidgetView

Initialization

public init(widgetId: Int, height: Binding<CGFloat>? = nil)
ParameterTypeDescription
widgetIdIntThe unique identifier for the widget to display
heightBinding<CGFloat>?Optional binding to track widget's dynamic height

Event Modifiers

All modifiers return a new StorifyMeWidgetView instance, enabling chaining.

.onStoriesLoaded(_:)

Called when stories are loaded successfully.

.onStoriesLoaded { widgetId, stories in
// widgetId: Int - The widget identifier
// stories: [StorifyMeStoryModel] - Array of loaded stories
}

.onError(_:)

Called when an error occurs during loading.

.onError { error in
// error: StorifyMeError - The error that occurred
print("Error code: \(error.code), message: \(error.message)")
}

.onStoryOpened(_:)

Called when a story is opened.

.onStoryOpened { story, index in
// story: StorifyMeStoryModel? - The opened story
// index: Int - Position in the widget
}

.onStoryFinished(_:)

Called when a story finishes playing.

.onStoryFinished { story, index in
// story: StorifyMeStoryModel? - The finished story
// index: Int - Position in the widget
}

.onStoryClose(_:)

Called when the story viewer is closed.

.onStoryClose { story in
// story: StorifyMeStoryModel? - The last viewed story
}

.onStoryShared(_:)

Called when a story is shared.

.onStoryShared { story in
// story: StorifyMeStoryModel? - The shared story
}

.onHeightChange(_:)

Called when the widget's height changes.

.onHeightChange { height in
// height: CGFloat - The new height value
}

.onAdDisplayed(_:)

Called when an ad is displayed within a story.

.onAdDisplayed { story, index in
// story: StorifyMeStoryModel - The story containing the ad
// index: Int - The ad index
}

Static Methods

clearGifCache()

Clears all cached GIF data from memory. Call this when navigating away from a screen with widgets.

.onDisappear {
StorifyMeWidgetView.clearGifCache()
}

clearAllCaches()

Clears all caches including GIF data and disk cache. Use for aggressive memory cleanup.

StorifyMeWidgetView.clearAllCaches()

StorifyMeSDKManager

StorifyMeSDKManager is an ObservableObject for reactive SDK state management in SwiftUI.

Published Properties

PropertyTypeDescription
sdkStateStorifyMeSDKStateCurrent SDK state (.inactive, .initializing, .ready, .failed)
isReadyBoolWhether the SDK is ready to use

Methods

shutdownSDK(completion:)

Shuts down the SDK and releases resources.

sdkManager.shutdownSDK {
print("SDK shut down")
}

reinitializeSDK(accountId:apiKey:env:completion:)

Reinitializes the SDK with new credentials.

sdkManager.reinitializeSDK(
accountId: "NEW_ACCOUNT_ID",
apiKey: "NEW_API_KEY",
env: .EU
) { result in
switch result {
case .success:
print("SDK reinitialized")
case .failure(let error):
print("Failed: \(error.message)")
}
}

Usage Example

@main
struct MyApp: App {
@StateObject private var sdkManager = StorifyMeSDKManager()

init() {
StorifyMeInstance.shared.initialize(
accountId: "YOUR_ACCOUNT_ID",
apiKey: "YOUR_API_KEY",
env: .EU
)
}

var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(sdkManager)
}
}
}

struct ContentView: View {
@EnvironmentObject var sdkManager: StorifyMeSDKManager

var body: some View {
Group {
if sdkManager.isReady {
StorifyMeWidgetView(widgetId: 54)
} else {
ProgressView("Loading SDK...")
}
}
}
}

Usage Patterns

In VStack

struct VStackExample: View {
var body: some View {
VStack(spacing: 16) {
Text("Welcome")
.font(.title)

StorifyMeWidgetView(widgetId: 54)
.frame(height: 200)

Text("More content below")
}
.padding()
}
}

In ScrollView

struct ScrollViewExample: View {
@State private var widgetHeight: CGFloat = 150

var body: some View {
ScrollView {
VStack(spacing: 16) {
Text("Header")
.font(.title)

StorifyMeWidgetView(widgetId: 54, height: $widgetHeight)
.frame(height: widgetHeight)

ForEach(0..<10) { index in
Text("Item \(index)")
.padding()
}
}
}
}
}

In List

struct ListExample: View {
@State private var widgetHeight: CGFloat = 150

var body: some View {
List {
Section("Stories") {
StorifyMeWidgetView(widgetId: 54, height: $widgetHeight)
.frame(height: widgetHeight)
.listRowInsets(EdgeInsets())
}

Section("Other Items") {
ForEach(0..<5) { index in
Text("Item \(index)")
}
}
}
}
}

With NavigationView/NavigationStack

When using navigation, clear GIF cache on disappear to prevent memory leaks:

struct NavigationExample: View {
var body: some View {
NavigationStack {
StoriesScreen()
}
}
}

struct StoriesScreen: View {
@State private var widgetHeight: CGFloat = 150

var body: some View {
ScrollView {
StorifyMeWidgetView(widgetId: 54, height: $widgetHeight)
.frame(height: widgetHeight)
}
.navigationTitle("Stories")
.onDisappear {
// Important: Clear GIF cache when navigating away
StorifyMeWidgetView.clearGifCache()
}
}
}

Memory Management

Cleanup on Navigation

SwiftUI's view lifecycle doesn't always trigger UIKit's cleanup methods reliably. Always clear GIF cache when navigating away:

.onDisappear {
StorifyMeWidgetView.clearGifCache()
}

iOS 17+ Considerations

In iOS 17+, dismantleUIView may not be called reliably (FB11979117). The SDK implements fallback cleanup in the Coordinator's deinit, but explicit cleanup via onDisappear is recommended.

Multiple Widgets

When displaying multiple widgets, consider clearing all caches when leaving the screen:

struct MultipleWidgetsView: View {
var body: some View {
ScrollView {
StorifyMeWidgetView(widgetId: 54)
.frame(height: 200)

StorifyMeWidgetView(widgetId: 55)
.frame(height: 200)
}
.onDisappear {
StorifyMeWidgetView.clearAllCaches()
}
}
}

Complete Examples

Full App Structure

import SwiftUI
import StorifyMe

@main
struct StorifyMeExampleApp: App {
@StateObject private var sdkManager = StorifyMeSDKManager()

init() {
StorifyMeInstance.shared.initialize(
accountId: "YOUR_ACCOUNT_ID",
apiKey: "YOUR_API_KEY",
env: .EU
)
}

var body: some Scene {
WindowGroup {
MainView()
.environmentObject(sdkManager)
}
}
}

struct MainView: View {
@EnvironmentObject var sdkManager: StorifyMeSDKManager
@State private var widgetHeight: CGFloat = 150
@State private var loadedCount: Int = 0

var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: 16) {
if sdkManager.isReady {
StorifyMeWidgetView(widgetId: 54, height: $widgetHeight)
.frame(height: widgetHeight)
.onStoriesLoaded { _, stories in
loadedCount = stories.count
}
.onError { error in
print("Widget error: \(error.message)")
}

Text("Loaded \(loadedCount) stories")
.font(.caption)
.foregroundColor(.secondary)
} else {
ProgressView("Loading SDK...")
}
}
.padding()
}
.navigationTitle("StorifyMe Demo")
.onDisappear {
StorifyMeWidgetView.clearGifCache()
}
}
}
}

Account Switching

struct AccountSwitchingView: View {
@EnvironmentObject var sdkManager: StorifyMeSDKManager
@State private var isSwitching = false

var body: some View {
VStack {
if isSwitching {
ProgressView("Switching account...")
} else {
StorifyMeWidgetView(widgetId: 54)
.frame(height: 200)

Button("Switch Account") {
switchAccount()
}
}
}
}

private func switchAccount() {
isSwitching = true

// Clear caches before switching
StorifyMeWidgetView.clearAllCaches()

sdkManager.reinitializeSDK(
accountId: "NEW_ACCOUNT_ID",
apiKey: "NEW_API_KEY",
env: .EU
) { result in
isSwitching = false

if case .failure(let error) = result {
print("Switch failed: \(error.message)")
}
}
}
}

Migration from Manual UIViewRepresentable

If you previously used a manual UIViewRepresentable wrapper, migration is straightforward:

Before (Manual Implementation)

struct OldStorifyMeWidgetView: UIViewRepresentable {
func makeCoordinator() -> Coordinator { ... }
func makeUIView(context: Context) -> StorifyMeWidget { ... }
func updateUIView(_ uiView: StorifyMeWidget, context: Context) { }
}

After (Native Wrapper)

// Simply use StorifyMeWidgetView directly
StorifyMeWidgetView(widgetId: 54, height: $widgetHeight)
.onStoriesLoaded { widgetId, stories in
// Handle loaded stories
}

Key Differences

AspectManual ImplementationNative Wrapper
Height bindingManual via sizeDelegateBuilt-in height parameter
Event handlingCoordinator + delegateModifier methods
Memory managementManual cleanupAutomatic + clearGifCache()
Code lines~80+ lines~5-10 lines