A lightweight SwiftUI framework for creating fluid morphing transitions between two pieces of content.
MorphingView combines SwiftUI view composition, an animatable blur progression, compositing, and a SwiftUI stitchable Metal shader to produce a smooth visual transformation between a source view and a destination view.
- iOS 18.0+
- Swift 6.4+
- Xcode 27+
- SwiftUI
- Metal / SwiftUI shader support
- SwiftUI-native API
- Two public APIs for different usage styles
- Animatable morphing progress
- Configurable blur radius
- Custom stitchable Metal shader for alpha thresholding
- Distributed as a prebuilt XCFramework
- Swift Package Manager binary target support
MorphingView is distributed as a binary Swift Package.
Add the package dependency to your project using the repository URL:
https://github.com/thakur-vijay/MorphingView.git
The package exposes the MorphingView library product.
After adding the dependency:
import MorphingViewMorphingView provides two public APIs.
Use this API when you want to explicitly provide both the source and destination views.
import SwiftUI
import MorphingView
struct ExampleView: View {
@State private var isActive = false
var body: some View {
MorphingView(
blurRadius: 8,
toggle: isActive
) {
Image(systemName: "heart")
.font(.system(size: 48))
} to: {
Image(systemName: "heart.fill")
.font(.system(size: 48))
}
.onTapGesture {
withAnimation(.easeInOut(duration: 0.4)) {
isActive.toggle()
}
}
}
}When toggle is false, the from content is displayed. When toggle is true, the to content is displayed.
When the current view should be the source of the morph, the modifier API is more concise.
import SwiftUI
import MorphingView
struct ExampleView: View {
@State private var isLiked = false
var body: some View {
Image(systemName: "heart")
.font(.system(size: 48))
.morphing(
blurRadius: 8,
toggle: isLiked
) {
Image(systemName: "heart.fill")
.font(.system(size: 48))
}
.onTapGesture {
withAnimation(.easeInOut(duration: 0.4)) {
isLiked.toggle()
}
}
}
}The modifier API follows this model:
self → from
to → destination
MorphingView does not own an animation. The transition is driven by SwiftUI's animation system.
For example:
withAnimation(.easeInOut(duration: 0.4)) {
isActive.toggle()
}This separation keeps the component composable with SwiftUI animation APIs.
Use a moderate blur radius and let the animation duration determine the character of the transition.
For example:
.morphing(
blurRadius: 8,
toggle: state
) {
DestinationView()
}The appropriate blur value depends on the size and visual complexity of the content. Higher values create a softer, more diffuse transition but can also increase rendering cost and reduce visual clarity.