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