Skip to content

dimzfresh/StoriesKit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

54 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“± StoriesKit

StoriesKit Demo

Swift iOS License

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!

GitHub stars GitHub forks

✨ Features

  • 🎨 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

πŸš€ Quick Start

Installation

Add StoriesKit to your project via Swift Package Manager:

dependencies: [
    .package(url: "https://github.com/dimzfresh/StoriesKit.git", from: "2.0.0")
]

Basic Usage

SwiftUI

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
                )
            }
        }
    }
}

UIKit

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)

πŸ“– Detailed Documentation

StoriesModel - Central Configuration

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

Data Models

StoriesGroupModel

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 */]
)

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
)

StoriesMediaModel

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

Video Player Features

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

Events

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"))
}

StoriesCarouselView Configuration

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
)

Corner Styles

// Circular carousel items (default)
corners: .circle

// Rounded rectangle with custom radius
corners: .radius(12)  // 12pt corner radius
corners: .radius(8)   // 8pt corner radius

The corner style applies to both the avatar images and their progress indicator rings, ensuring visual consistency across all carousel items.

Custom Content with Buttons

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
)

πŸŽ₯ Video Support Examples

Video Stories with Custom Content

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
    )
]

Mixed Media Stories

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
    )
]

πŸš€ Integration Examples

UIKit + SwiftUI Carousel

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.

🎨 Customization

Colors and Styles

  • Configure backgroundColor for story backgrounds
  • Use AttributedString for rich text formatting
  • Customize button colors and corner rounding

Timers

  • Set duration for each story (default 4 seconds)
  • Timer automatically pauses on tap and resumes on release

Images

  • URL loading support with automatic caching
  • Placeholder images for better UX
  • Smooth transitions between images

πŸ—οΈ Architecture

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

Main Components

  • Stories β€” main class for creating stories
  • StoriesModel β€” centralized configuration model
  • StoriesStateManager β€” centralized state management
  • StoriesVideoManager β€” per-session video playback
  • ContainerView β€” SwiftUI container for stories
  • ContentView β€” main content with navigation
  • PageView β€” individual story page
  • ViewModel β€” state management and logic
  • ViewController β€” UIKit presentation
  • ProgressBarView β€” progress indicator
  • StoriesMediaView β€” universal media display (images/videos)
  • VideoPlayerView β€” advanced video player component

Events and State

  • ViewEvent β€” user events (taps, swipes, timers)
  • ViewState β€” current state (groups, progress, indices)
  • StoriesStateManager.Event β€” events for links, navigation, and viewed state
  • StoriesVideoManager.State β€” video playback states (idle, playing, paused)

State Management

  • 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

πŸ“± Requirements

  • iOS 16.0+
  • Swift 5.9+
  • Xcode 15.0+

πŸ”§ Dependencies

  • Nuke β€” for image loading (NukeUI)

πŸ“„ License

StoriesKit is distributed under the MIT license. See the LICENSE file for details.

🀝 Contributing

We welcome contributions to StoriesKit! Please read our contributing guidelines.

Development

# 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 lint

CI runs tests and SwiftLint on every push to main via GitHub Actions.

πŸ†• What's New

Version 2.0 Features

  • πŸŽ₯ 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

Migration Guide

If you're upgrading from version 1.x:

  1. Replace StoriesImageModel with StoriesMediaModel
  2. Use StoriesModel for configuration instead of individual parameters
  3. Update to new StoriesPageModel structure with mediaSource
  4. Add video support using .video() media type
  5. Buttons in custom content β€” Move buttons from StoriesPageModel.button to custom content views
  6. Carousel configuration β€” Use StoriesCarouselConfiguration for carousel customization
  7. Corner styles β€” Use Layout.CornerStyle for carousel item shapes

πŸ“ž Support

If you have questions or suggestions, create an issue or contact us.


Made with ❀️ for iOS developers

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors