An elegant, powerful skeleton loading animation library for SwiftUI
SwiftUISkeletonLoader provides a seamless way to show users that content is loading while preparing them for what's coming. With just a few lines of code, you can add beautiful, customizable skeleton animations to any SwiftUI view.
- π¨ Beautiful Animations - 4 distinct, professionally designed animation styles
- π― Dead Simple API - Just add
.skeleton(isActive: true)to any view - π§ Fully Customizable - Colors, gradients, shapes, effects, and timing
- π§ Smart Detection - Auto-calculates skeleton lines based on content
- β‘ Performance Optimized - Smooth 60fps animations with minimal overhead
- βΏ Accessibility Built-in - Screen reader support out of the box
- π¦ Zero Dependencies - Pure SwiftUI, no external libraries
- π Well Documented - Comprehensive examples and guides
- Easy to use - Just add
.skeleton(isActive: true)to any view - 4 Animation Types - Pulse, Sliding, Shimmer, Wave
- Highly customizable - Colors, gradients, animations, shapes, and more
- SwiftUI native - Built specifically for SwiftUI with modern Swift features
- Universal - Works on iOS, macOS, tvOS, and watchOS
- Lightweight - Small footprint, readable codebase
- Multiline support - Perfect for text content of any length
- List support - Built-in skeleton list component
- Custom shapes - Circle, Capsule, RoundedRectangle, or any custom shape
- Visual effects - Bordered, Glowing, Glassmorphism, Shadowed
- Smart skeleton - Auto-detects content dimensions
- Delayed loading - Prevents UI flash for fast-loading content
- Synchronized groups - Coordinate multiple skeleton animations
SkeletonUI offers 4 distinct animation styles, each designed for different use cases:
| Animation | Description | Best For | Visual Style |
|---|---|---|---|
| Pulse | Gentle breathing opacity effect that fades in and out | Simple loading states, minimal distraction | Subtle, calm |
| Sliding | Gradient band slides smoothly across the skeleton in 8 directions | Directional content loading, modern UI | Dynamic, directional |
| Shimmer | Facebook-style shimmer effect with a bright highlight sweep | Social feeds, card layouts, professional apps | Polished, familiar |
| Wave | Smooth flowing wave with gradual fade-in/out across the skeleton | Elegant loading, premium feel | Sophisticated, flowing |
A gentle, breathing effect where the skeleton smoothly fades between two opacity levels. Perfect for minimal, distraction-free loading states.
Characteristics:
- Opacity animates from 30% to 80%
- Smooth easeInOut timing
- Auto-reverses for continuous breathing effect
- Default duration: 1.0 second
Use when: You want subtle, non-distracting loading feedback
A gradient band that slides across the skeleton. Supports 8 directions for maximum flexibility.
Directions: leftToRight, rightToLeft, topToBottom, bottomToTop, topLeftToBottomRight, bottomRightToTopLeft, topRightToBottomLeft, bottomLeftToTopRight
Characteristics:
- Linear movement with gradient fade
- Customizable direction
- Suggests directional content loading
- Default duration: 1.5 seconds
Use when: You want to suggest content is flowing in from a direction
Inspired by Facebook's loading shimmer, a bright highlight sweeps across the skeleton with high contrast.
Characteristics:
- Bright white highlight with 60% opacity
- Linear movement for consistent speed
- High visibility and familiarity
- Default duration: 1.2 seconds
Use when: You want a polished, professional look that users recognize
A smooth, flowing wave with gradual fade-in and fade-out, creating an elegant, premium feel.
Characteristics:
- 9-step gradient for smooth wave curve
- EaseInOut timing for natural flow
- Wider coverage (80% of skeleton width)
- Gradual fade creates depth
- Default duration: 1.8 seconds
Use when: You want an elegant, sophisticated loading experience
// Pulse with custom color
Text("Loading...").skeleton(
isActive: true,
animation: .pulse(),
color: .blue.opacity(0.4)
)
// Shimmer with custom color
Text("Loading...").skeleton(
isActive: true,
animation: .shimmer(),
color: .purple.opacity(0.4)
)
// Wave with custom color
Text("Loading...").skeleton(
isActive: true,
animation: .wave(),
color: .green.opacity(0.4)
)let gradient = SkeletonGradient(
baseColor: .blue.opacity(0.3),
highlightColor: .cyan.opacity(0.1)
)
Text("Loading...").skeleton(
isActive: true,
animation: .sliding(direction: .leftToRight),
gradient: gradient
)// Fast pulse
Text("Quick").skeleton(
isActive: true,
animation: .pulse(duration: 0.6)
)
// Slow wave
Text("Relaxed").skeleton(
isActive: true,
animation: .wave(duration: 3.0)
)
// Custom sliding speed
Text("Custom").skeleton(
isActive: true,
animation: .sliding(direction: .topToBottom, duration: 2.0)
)dependencies: [
.package(url: "https://github.com/YourUsername/SkeletonUI.git", from: "1.0.0")
]- In Xcode, go to File β Add Package Dependencies...
- Enter:
https://github.com/YourUsername/SkeletonUI.git - Click Add Package
Just 3 steps to add skeleton loading:
import SkeletonUIText("Loading content...")
.skeleton(isActive: true)// Solid skeleton
Text("Content").skeleton(isActive: isLoading)
// Animated pulse
Text("Content").animatedSkeleton(isActive: isLoading)
// Gradient sliding
Text("Content").gradientSkeleton(isActive: isLoading)struct ContentView: View {
@State private var isLoading = true
var body: some View {
VStack {
Text("Hello, World!")
.skeleton(isActive: isLoading)
Button("Toggle") {
isLoading.toggle()
}
}
}
}Text("This is a long text that spans multiple lines")
.skeleton(isActive: isLoading, lines: 3)// Pulse animation
Text("Pulse effect")
.animatedSkeleton(
isActive: isLoading,
animation: .pulse(duration: 1.0)
)
// Sliding gradient
Text("Sliding gradient")
.gradientSkeleton(
isActive: isLoading,
animation: .sliding(direction: .leftToRight)
)
// Shimmer effect
Text("Shimmer effect")
.skeleton(
isActive: isLoading,
animation: .shimmer(duration: 1.2)
)let customAppearance = SkeletonAppearance(
tintColor: .blue.opacity(0.3),
cornerRadius: 8,
multilineHeight: 20
)
Text("Custom skeleton")
.skeleton(
isActive: isLoading,
appearance: customAppearance
)struct ListView: View {
@State private var isLoading = true
@State private var items: [Item] = []
var body: some View {
SkeletonList(
isActive: isLoading,
rowCount: 10,
rowHeight: 80
) {
List(items) { item in
ItemRow(item: item)
}
}
}
}struct CardView: View {
let isLoading: Bool
var body: some View {
VStack(alignment: .leading) {
// Image placeholder
Rectangle()
.frame(height: 200)
.skeleton(isActive: isLoading, animation: .shimmer())
// Title
Text("Card Title")
.font(.headline)
.skeleton(isActive: isLoading)
// Description
Text("Card description text")
.skeleton(isActive: isLoading, lines: 2)
}
.cornerRadius(12)
}
}SkeletonUI comes with 20 predefined colors inspired by flat design:
Text("Colorful skeleton")
.skeleton(
isActive: true,
appearance: SkeletonAppearance(tintColor: .skeletonTurquoise)
)Available colors: .skeletonTurquoise, .skeletonEmerald, .skeletonPeterRiver, .skeletonAmethyst, .skeletonSunFlower, .skeletonCarrot, .skeletonAlizarin, and more.
let gradient = SkeletonGradient(
baseColor: .blue.opacity(0.3),
highlightColor: .blue.opacity(0.1)
)
Text("Gradient skeleton")
.gradientSkeleton(isActive: true, gradient: gradient)// Different sliding directions
.skeleton(isActive: true, animation: .sliding(direction: .leftToRight))
.skeleton(isActive: true, animation: .sliding(direction: .topToBottom))
.skeleton(isActive: true, animation: .sliding(direction: .topLeftToBottomRight))
// Custom durations
.skeleton(isActive: true, animation: .pulse(duration: 2.0))
.skeleton(isActive: true, animation: .wave(duration: 3.0))Text("Smooth transition")
.skeleton(
isActive: isLoading,
transition: .opacity(duration: 0.5)
)
Text("Scale transition")
.skeleton(
isActive: isLoading,
transition: .scale(duration: 0.3, scale: 0.95)
)// Set global defaults
SkeletonAppearance.default.tintColor = .gray.opacity(0.2)
SkeletonAppearance.default.cornerRadius = 8
SkeletonAppearance.default.multilineHeight = 18struct ProfileView: View {
@State private var user: User?
var body: some View {
VStack {
Text(user?.name ?? "Loading...")
.skeleton(isActive: user == nil)
Text(user?.bio ?? "Loading bio...")
.skeleton(isActive: user == nil, lines: 3)
}
}
}struct ComplexView: View {
let isLoading: Bool
var body: some View {
VStack {
HStack {
Circle()
.frame(width: 50, height: 50)
.skeleton(isActive: isLoading)
VStack(alignment: .leading) {
Text("Name")
.skeleton(isActive: isLoading)
Text("Subtitle")
.skeleton(isActive: isLoading)
}
Spacer()
}
Text("Description content here")
.skeleton(isActive: isLoading, lines: 4)
}
}
}SkeletonUI is designed to be performant:
- Efficient animations using SwiftUI's native animation system
- Minimal memory footprint with lightweight view modifiers
- Optimized rendering with conditional view updates
- Smooth frame rates even with complex layouts
If you're coming from the UIKit SkeletonView library, here's how to migrate:
| SkeletonView (UIKit) | SkeletonUI (SwiftUI) |
|---|---|
view.showSkeleton() |
.skeleton(isActive: true) |
view.showAnimatedSkeleton() |
.animatedSkeleton(isActive: true) |
view.showGradientSkeleton() |
.gradientSkeleton(isActive: true) |
view.hideSkeleton() |
.skeleton(isActive: false) |
We welcome contributions! Here's how you can help:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
SkeletonUI is available under the MIT license. See the LICENSE file for more info.
- Built with β€οΈ for the SwiftUI community
Star β this repository if it helped you!