SwiftUI Integration
This guide covers integrating StorifyMe into SwiftUI applications using the native StorifyMeWidgetView wrapper.
- 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)
| Parameter | Type | Description |
|---|---|---|
widgetId | Int | The unique identifier for the widget to display |
height | Binding<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
| Property | Type | Description |
|---|---|---|
sdkState | StorifyMeSDKState | Current SDK state (.inactive, .initializing, .ready, .failed) |
isReady | Bool | Whether 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
| Aspect | Manual Implementation | Native Wrapper |
|---|---|---|
| Height binding | Manual via sizeDelegate | Built-in height parameter |
| Event handling | Coordinator + delegate | Modifier methods |
| Memory management | Manual cleanup | Automatic + clearGifCache() |
| Code lines | ~80+ lines | ~5-10 lines |