StoriesKit is a modern Swift library for creating beautiful Instagram-style stories with support for both UIKit and SwiftUI. The library provides ready-to-use components for displaying stories with navigation, timers, and interactive elements.
β Like this project? Give it a star on GitHub! Your support helps me continue development and add new features.
π Want to see more? Follow me for updates and new releases!
- π¨ Beautiful Design β Modern UI in the style of popular social networks
- β‘ High Performance β Optimized architecture using SwiftUI and Combine
- πΌοΈ Media Support β Images and videos with smooth playback
- π₯ Video Playback β Advanced video player with preloading and state management
- β±οΈ Smart Timers β Configurable story duration with video synchronization
- π― Interactivity β Support for buttons, links, and gestures
- π± Responsive β Support for various screen sizes
- π Navigation β Smooth transitions between stories and groups
- ποΈ Flexible Customization β Rich customization options
- ποΈ Dual Platform Support β Works in both UIKit and SwiftUI
- πͺ Custom Content β Support for custom SwiftUI views in stories
- π¨ Theming β Centralized configuration with StoriesModel
Add StoriesKit to your project via Swift Package Manager:
dependencies: [
.package(url: "https://github.com/dimzfresh/StoriesKit.git", from: "2.0.0")
]import StoriesKit
import SwiftUI
struct ContentView: View {
@StateObject private var stateManager: StoriesStateManager
@Namespace private var avatarNamespace
init() {
let model = StoriesModel(
groups: [
StoriesGroupModel(
id: "user1",
title: "User 1",
avatarImage: .remote("https://example.com/avatar.jpg"),
pages: [
StoriesPageModel(
date: "Today",
mediaSource: StoriesMediaModel(
media: .image(.remote("https://example.com/story.jpg"))
),
duration: 5.0
)
]
)
],
backgroundColor: .black,
user: .init()
)
_stateManager = .init(wrappedValue: .init(model: model))
}
var body: some View {
StoriesCarouselView(
stateManager: stateManager,
avatarNamespace: avatarNamespace
)
.overlay {
if stateManager.state.isShown {
Stories.build(
stateManager: stateManager,
avatarNamespace: avatarNamespace
)
}
}
}
}Present stories from a SwiftUI wrapper (required for Namespace matched geometry with the carousel):
import StoriesKit
import SwiftUI
private struct StoriesPresentationView: View {
@ObservedObject var stateManager: StoriesStateManager
@Namespace private var avatarNamespace
var body: some View {
Stories.build(
stateManager: stateManager,
avatarNamespace: avatarNamespace
)
}
}
// Present from UIKit
let hostingController = UIHostingController(
rootView: StoriesPresentationView(stateManager: stateManager)
)
hostingController.modalPresentationStyle = .overFullScreen
present(hostingController, animated: true)The new StoriesModel provides centralized configuration for all StoriesKit components:
let storiesModel = StoriesModel(
groups: [/* StoriesGroupModel array */],
backgroundColor: .black,
progress: StoriesModel.Progress(
lineSize: 3.0,
interItemSpacing: 4.0,
containerPadding: .init(top: 4, leading: 0, bottom: 0, trailing: 0),
viewedColor: .gray.opacity(0.6),
unviewedColor: .green
),
user: StoriesModel.UserModel(
avatar: StoriesModel.Avatar(size: 30),
userName: StoriesModel.Text(font: .system(size: 12, weight: .bold)),
date: StoriesModel.Text(
font: .system(size: 10, weight: .semibold),
color: .white.opacity(0.8)
)
)
)Represents a group of stories (e.g., stories from one user):
StoriesGroupModel(
id: "unique_id",
title: "Group Title",
avatarImage: .remote("https://example.com/avatar.jpg"),
pages: [/* array of StoriesPageModel */]
)Individual story page with support for images, videos, and custom content:
// Image story
StoriesPageModel(
date: "Today",
mediaSource: StoriesMediaModel(
media: .image(.remote("https://example.com/image.jpg"))
),
duration: 4.0,
padding: EdgeInsets(top: 54, leading: 0, bottom: 44, trailing: 0),
cornerRadius: 12,
content: AnyView(
VStack(spacing: 0) {
Text("Story Title")
.font(.title)
.foregroundColor(.white)
.padding(.top, 32)
.padding(.horizontal, 16)
Text("Story Subtitle")
.font(.body)
.foregroundColor(.white.opacity(0.9))
.padding(.top, 8)
.padding(.horizontal, 16)
// Custom buttons in content
VStack(spacing: 12) {
Button("Next") {
// Handle next action
}
.frame(width: 148, height: 50)
.background(Color.blue)
.clipShape(RoundedRectangle(cornerRadius: 12))
Button("Learn More") {
// Handle link action
}
.frame(width: 148, height: 50)
.background(Color.green)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
.padding(.bottom, 24)
}
)
)
// Video story
StoriesPageModel(
date: "Yesterday",
mediaSource: StoriesMediaModel(
media: .video(.remote("https://example.com/video.mp4"))
),
duration: 8.0,
padding: EdgeInsets(top: 54, leading: 0, bottom: 44, trailing: 0),
cornerRadius: 12
)Model for media (images and videos) with support for various sources:
// Image media
StoriesMediaModel(
media: .image(.remote("https://example.com/image.jpg")) // String?
// media: .image(.remote(optionalURL)) // URL?
// media: .image(.local(UIImage))
)
// Video media
StoriesMediaModel(
media: .video(.remote("https://example.com/video.mp4"))
)
// Local video
StoriesMediaModel(
media: .video(.local(AVAsset(url: localVideoURL)))
)StoriesKit includes an advanced video player with:
- Preloading - Videos are preloaded to avoid black screen flickering
- State Management - Centralized video player state management
- Timer Synchronization - Video playback is synchronized with story timers
- Smooth Transitions - Seamless switching between videos
- Memory Efficient - Single player instance reused across all videos
Handle user actions via StoriesStateManager.Event:
.onChange(of: stateManager.state.event) { event in
guard let event else { return }
switch event {
case let .didOpenLink(urlString):
if let url = URL(string: urlString) {
openURL(url)
}
case .didToggleGroup, .didSwitchGroup, .didViewPage:
break
}
}Custom story content can emit link events via @Environment(\.storiesStateManager):
@Environment(\.storiesStateManager) private var stateManager
Button("Learn More") {
stateManager?.send(.didOpenLink("https://example.com"))
}The carousel supports both circular and rounded rectangle corner styles:
let configuration = StoriesCarouselConfiguration(
layout: StoriesCarouselConfiguration.Layout(
itemSpacing: 16,
horizontalPadding: 16,
corners: .radius(12) // Rounded rectangle with 12pt radius
),
avatar: StoriesCarouselConfiguration.Avatar(
size: 70,
progressPadding: 6
),
progress: StoriesCarouselConfiguration.Progress(
lineWidth: 3,
gap: 3,
viewedColor: .gray.opacity(0.6),
unviewedColor: .green.opacity(0.8)
)
)
StoriesCarouselView(
stateManager: stateManager,
avatarNamespace: avatarNamespace,
configuration: configuration
)// Circular carousel items (default)
corners: .circle
// Rounded rectangle with custom radius
corners: .radius(12) // 12pt corner radius
corners: .radius(8) // 8pt corner radiusThe corner style applies to both the avatar images and their progress indicator rings, ensuring visual consistency across all carousel items.
Buttons are now integrated directly into custom content views:
StoriesPageModel(
date: "Today",
mediaSource: StoriesMediaModel(
media: .image(.remote("https://example.com/background.jpg"))
),
content: AnyView(
VStack(spacing: 0) {
Text("Welcome to Stories")
.font(.title)
.foregroundColor(.white)
.padding(.top, 32)
.padding(.horizontal, 16)
Text("Discover amazing content")
.font(.body)
.foregroundColor(.white.opacity(0.9))
.padding(.top, 8)
.padding(.horizontal, 16)
// Custom buttons with actions
VStack(spacing: 12) {
Button("Get Started") {
// Handle button action
}
.frame(width: 148, height: 50)
.background(Color.blue)
.clipShape(RoundedRectangle(cornerRadius: 12))
Button("Learn More") {
// Handle link action
}
.frame(width: 148, height: 50)
.background(Color.green)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
.padding(.bottom, 24)
}
),
duration: 6.0
)let videoStories = [
StoriesPageModel(
title: AttributedString("Amazing Video"),
subtitle: AttributedString("Check out this cool content"),
backgroundColor: .black,
mediaSource: StoriesMediaModel(
media: .video(.remote("https://example.com/video.mp4"))
),
content: AnyView(
VStack {
Text("π¬ Video Story")
.font(.title)
.foregroundColor(.white)
Text("Tap to interact")
.font(.caption)
.foregroundColor(.white.opacity(0.8))
}
),
duration: 10.0
)
]let mixedStories = [
// Image story
StoriesPageModel(
title: AttributedString("Photo Story"),
mediaSource: StoriesMediaModel(
media: .image(.remote("https://example.com/photo.jpg"))
),
duration: 4.0
),
// Video story
StoriesPageModel(
title: AttributedString("Video Story"),
mediaSource: StoriesMediaModel(
media: .video(.remote("https://example.com/video.mp4"))
),
duration: 8.0
)
]Use StoriesStateManager as the single source of truth. Embed the carousel and present the viewer from SwiftUI (see Quick Start). For UIKit hosts, wrap the SwiftUI presentation view in UIHostingController and observe stateManager.state.event for links and analytics.
See the included StoriesExample app for a full carousel + overlay integration.
- Configure
backgroundColorfor story backgrounds - Use
AttributedStringfor rich text formatting - Customize button colors and corner rounding
- Set
durationfor each story (default 4 seconds) - Timer automatically pauses on tap and resumes on release
- URL loading support with automatic caching
- Placeholder images for better UX
- Smooth transitions between images
StoriesKit is built on modern architecture using:
- SwiftUI β for UI components
- Combine β for reactive programming
- MVVM β architectural pattern
- Nuke / NukeUI β for image loading and caching
- AVFoundation β for video playback
Storiesβ main class for creating storiesStoriesModelβ centralized configuration modelStoriesStateManagerβ centralized state managementStoriesVideoManagerβ per-session video playbackContainerViewβ SwiftUI container for storiesContentViewβ main content with navigationPageViewβ individual story pageViewModelβ state management and logicViewControllerβ UIKit presentationProgressBarViewβ progress indicatorStoriesMediaViewβ universal media display (images/videos)VideoPlayerViewβ advanced video player component
ViewEventβ user events (taps, swipes, timers)ViewStateβ current state (groups, progress, indices)StoriesStateManager.Eventβ events for links, navigation, and viewed stateStoriesVideoManager.Stateβ video playback states (idle, playing, paused)
- Centralized State β All state managed through
StoriesStateManager - Video Synchronization β Video playback synchronized with story timers
- Memory Efficient β Single video player instance reused across all videos
- Reactive Updates β UI updates automatically when state changes
- iOS 16.0+
- Swift 5.9+
- Xcode 15.0+
- Nuke β for image loading (
NukeUI)
StoriesKit is distributed under the MIT license. See the LICENSE file for details.
We welcome contributions to StoriesKit! Please read our contributing guidelines.
# Install git hooks (SwiftLint on commit)
./Scripts/install-git-hooks.sh
# Run unit tests (iOS Simulator)
./Scripts/run-tests.sh
# Lint
./Scripts/run-swiftlint.sh lintCI runs tests and SwiftLint on every push to main via GitHub Actions.
- π₯ Video Support β Full video playback with preloading and state management
- π¨ StoriesModel β Centralized configuration for all components
- πͺ Custom Content β Support for custom SwiftUI views in stories with embedded buttons
- β‘ Performance β Optimized video player with single instance reuse
- π State Management β Centralized state management with StoriesStateManager
- π― Timer Sync β Video playback synchronized with story timers
- π¨ Theming β Rich customization options for all UI components
- π± Carousel Corners β Support for both circular and rounded rectangle carousel items
- ποΈ Custom Buttons β Buttons now integrated into custom content views
- π Flexible Layout β Custom padding and corner radius for story pages
If you're upgrading from version 1.x:
- Replace
StoriesImageModelwithStoriesMediaModel - Use
StoriesModelfor configuration instead of individual parameters - Update to new
StoriesPageModelstructure withmediaSource - Add video support using
.video()media type - Buttons in custom content β Move buttons from
StoriesPageModel.buttonto customcontentviews - Carousel configuration β Use
StoriesCarouselConfigurationfor carousel customization - Corner styles β Use
Layout.CornerStylefor carousel item shapes
If you have questions or suggestions, create an issue or contact us.
Made with β€οΈ for iOS developers
