This guide covers everything you need to integrate MorphModalKit into your SwiftUI app.
- In Xcode, choose File ▶ Add Packages…
- Enter the URL:
https://github.com/jsmmth/MorphModalKit.git - Click Add Package, select your target(s), and finish.
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MyApp",
platforms: [.iOS(.v15)],
dependencies: [
.package(url: "https://github.com/jsmmth/MorphModalKit.git", from: "0.0.1"),
],
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "MorphModalKit", package: "MorphModalKit")
]
)
]
)- Import in your SwiftUI root view:
import SwiftUI import MorphModalKit
- Instantiate a shared manager:
@StateObject private var modalManager = MorphModalManager()
- Overlay the host:
ZStack { // Your app content... } .overlay( MorphModalHost(manager: modalManager) .ignoresSafeArea() )
Use the builder APIs on your MorphModalManager to control presentation:
// Present a SwiftUI view
modalManager
.present(MyModalView())
.withSticky(MyStickyView.self)
.withOptions { opts in
opts.usesSnapshotsForMorph = true
}
// Push onto the navigation stack
modalManager
.push(MyOtherModal())
.inheritSticky() // reuse previous sticky
.noSticky() // disable sticky
// Pop or hide
modalManager.pop()
modalManager.hide()
// Morph (replace) the top card
modalManager.replace(
NextModalView(),
direction: .forward,
animation: .slide(100)
)-
.withSticky(_ stickyView: S.Type)
Attach a new SwiftUI sticky view subclass ofDefaultConstructible. -
.inheritSticky()
Reuse the sticky view from the previous modal. -
.noSticky()
Disable sticky elements for this modal. -
.withOptions { opts in ... }
Modify the underlyingModalOptions(inset, shadows, animation, etc). You can read the full configuration from the main README.
Your SwiftUI modal must conform to MorphModalContent:
struct MyModal: View, MorphModalContent {
@EnvironmentObject var modalManager: MorphModalManager
var modalConfig: MorphModalConfiguration = {
var cfg = MorphModalConfiguration()
cfg.canDismiss = true
cfg.preferredHeight = { width in 300 }
return cfg
}()
var body: some View {
// Your SwiftUI content...
}
}modalConfig.canDismiss: allow swipe‐down / overlay-tapmodalConfig.preferredHeight: desired card heightmodalConfig.dismissalScrollView: assign aUIScrollViewfor pull‐to‐dismiss support
Define a SwiftUI sticky view:
public struct MyStickyView: View, DefaultConstructible {
@EnvironmentObject var modalManager: MorphModalManager
public init() {}
public var body: some View {
Text("Sticky Header")
.frame(maxWidth: .infinity)
.background(Color(.secondarySystemBackground))
}
}Attach via .withSticky(MyStickyView.self) on present or push.
Wrap scrollable SwiftUI inside a UIScrollView representable:
struct DismissableScroll<Content: View>: UIViewRepresentable {
let content: Content
@Binding var scrollView: UIScrollView?
func makeUIView(context: Context) -> UIScrollView {
let scroll = UIScrollView()
scroll.alwaysBounceVertical = true
// embed SwiftUI via UIHostingController...
DispatchQueue.main.async { scrollView = scroll }
return scroll
}
func updateUIView(_ uiView: UIScrollView, context: Context) {}
}In your modal view:
@State private var uiScroll: UIScrollView?
var body: some View {
DismissableScroll(content: MyContent(), scrollView: $uiScroll)
.onChange(of: uiScroll) { old, new in
guard let sv = new,
let wrapper = modalManager.currentFrontModal as? SwiftUIModalWrapper
else { return }
wrapper.dismissalHandlingScrollView = sv
modalManager.refreshScrollBinding()
}
}- InputModal: adapts height when keyboard appears
- ScrollModal: scrollable content with pull‐to‐dismiss
- MorphModal Steps: demonstrates replace animations
See Examples/SwiftUIExample/Modals for a working demo using only the package’s APIs. Feel free to swap in your own UI—this example is just a starting point.
Happy morphing! 🚀