A lightweight, dependency-free shimmer loading effect built entirely with SwiftUI.
Shimmer preserves the layout of your real interface, applies native placeholder redaction, and animates an adaptive highlight across the rendered content. It supports composite view hierarchies, light and dark appearances, and loading representations for controls that SwiftUI does not redact automatically.
Screen.Recording.2026-07-09.at.10.21.17.AM.mov
Preview video
Add the package preview recording here.
- One-line integration on any SwiftUI view hierarchy.
- Native placeholder redaction with no duplicate content model.
- Smooth diagonal highlight animation.
- Automatic light and dark appearance blending.
- Loading-specific
ToggleStyleandProgressViewStyle. - Full-width shimmer rendering for wide cards and containers.
- Interaction disabled automatically while loading.
- Dynamic text wrapping and existing layout dimensions preserved.
- No external dependencies.
- DEBUG-only interactive preview gallery.
| Platform | Minimum version |
|---|---|
| iOS | 17.0 |
| macOS | 14.0 |
| tvOS | 17.0 |
| watchOS | 10.0 |
| Swift | 6.0 |
- Open your project in Xcode.
- Select File > Add Package Dependencies.
- Enter the repository URL:
https://github.com/sonmbol/Shimmer.git
- Add the
Shimmerproduct to your application target.
Add Shimmer to your package dependencies:
dependencies: [
.package(
url: "https://github.com/sonmbol/Shimmer.git",
from: "1.0.0"
)
]Then add it to the dependencies of your target:
.target(
name: "YourTarget",
dependencies: [
.product(name: "Shimmer", package: "Shimmer")
]
)Import Shimmer, provide a loading binding, and apply the modifier to the
content that should become a placeholder:
import Shimmer
import SwiftUI
struct ProfileView: View {
@State private var isLoading = true
var body: some View {
HStack(alignment: .top, spacing: 12) {
Image(systemName: "person.crop.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 56, height: 56)
VStack(alignment: .leading, spacing: 6) {
Text("Ahmed Suliman")
.font(.headline)
Text("Senior iOS Engineer")
.font(.subheadline)
ProgressView(value: 0.65)
}
Spacer()
}
.shimmer(if: $isLoading)
.task {
try? await Task.sleep(for: .seconds(2))
isLoading = false
}
}
}When isLoading is true, Shimmer keeps the hierarchy's natural layout and
renders its loading representation. When the value becomes false, the
original content is restored.
SwiftUI's native placeholder redaction primarily targets text and images. Controls such as switches and progress indicators retain their normal chrome. Shimmer installs private loading-only styles inside the modifier to provide consistent placeholders:
Togglepreserves its label and replaces the switch with a placeholder capsule.ProgressViewpreserves optional labels and renders a full-width placeholder track.- Buttons retain their existing shape while their content is redacted and neutralized.
The original styles return automatically as soon as loading finishes.
The modified hierarchy does not accept pointer or touch input while loading:
.allowsHitTesting(false)This prevents placeholder buttons, toggles, links, and other controls from performing actions before their content is ready.
The highlight adapts to the active ColorScheme:
- Light appearance uses a clear-to-white highlight.
- Dark appearance uses a translucent white highlight with screen blending.
Control tint and saturation are neutralized only during loading, producing a consistent skeleton appearance without altering the loaded interface.
The package includes ShimmerModifier+Preview.swift, which demonstrates:
- Single-line and multiline text.
- Images and labels.
- Buttons, toggles, and progress indicators.
- Composite cards.
- Repeated collection rows.
- Loading-state toggling.
- Light and dark appearance switching.
The entire preview file is guarded by #if DEBUG, so preview code is excluded
from release builds.
public extension View {
func shimmer(if isLoading: Binding<Bool>) -> some View
}public struct ShimmerModifier: ViewModifier {
public init(isLoading: Binding<Bool>)
}The extension is the recommended integration:
content.shimmer(if: $isLoading)The modifier can also be applied directly:
content.modifier(ShimmerModifier(isLoading: $isLoading))Shimmer operates on the composed SwiftUI hierarchy and does not inspect private view internals. It relies on native redaction for standard content and public control-style APIs for controls requiring explicit loading geometry.
The package intentionally avoids runtime reflection, private SwiftUI APIs, and recursive subview traversal. This keeps rendering predictable and suitable for production applications.
Issues and pull requests are welcome. Please include:
- A concise description of the behavior.
- The affected platform and OS version.
- A minimal reproduction or preview when reporting a visual issue.
Before opening a pull request, verify both configurations:
swift build
swift build -c releaseShimmer is released under the MIT License.