Use when building or modifying SwiftUI views and need to visually verify the implementation matches requirements...
Storybook-SwiftUI enables instant visual verification of SwiftUI views during development. Tag views with unique identifiers, launch the app directly to Storybook, and automatically navigate to the specific view being developed.
Core principle: Visual verification should be as fast as running a test - tag it, launch it, see it.
Use this workflow when:
Don't use when:
To enable direct launch to Storybook, configure your app's launch arguments:
In your app's entry point or SceneDelegate:
import Storybook
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
if ProcessInfo.processInfo.arguments.contains("--storybook") {
Storybook.render()
} else {
ContentView()
}
}
}
}
Edit Scheme → Run → Arguments → Arguments Passed On Launch:
--storybookTip: Create a separate scheme called "Storybook" with this argument always enabled.
When creating or modifying a view, add a unique tag using a UUID:
extension Storybook {
@objc static let myButton = StorybookPage(
folder: "/Design System/Buttons",
views: [
PrimaryButton()
.storybookTitle("Primary")
.storybookTags("dev-12a34b56") // Unique tag for this session
]
)
}
Why UUIDs work well:
# macOS/Linux
uuidgen | tr '[:upper:]' '[:lower:]' | cut -c1-8
# Example: 3f1b16a2
# Or in code
UUID().uuidString.lowercased().prefix(8)
digraph verification_flow {
"Add unique tag to view" [shape=box];
"Build and run with --storybook" [shape=box];
"Search for tag" [shape=box];
"Auto-navigate?" [shape=diamond];
"View opens automatically" [shape=box];
"Tap result to open" [shape=box];
"Verify visually" [shape=box];
"Make changes" [shape=box];
"Hot reload or rebuild" [shape=box];
"Done?" [shape=diamond];
"Remove dev tag" [shape=box];
"Complete" [shape=box];
"Add unique tag to view" -> "Build and run with --storybook";
"Build and run with --storybook" -> "Search for tag";
"Search for tag" -> "Auto-navigate?";
"Auto-navigate?" -> "View opens automatically" [label="single result"];
"Auto-navigate?" -> "Tap result to open" [label="multiple"];
"View opens automatically" -> "Verify visually";
"Tap result to open" -> "Verify visually";
"Verify visually" -> "Done?";
"Done?" -> "Remove dev tag" [label="yes"];
"Done?" -> "Make changes" [label="no"];
"Make changes" -> "Hot reload or rebuild";
"Hot reload or rebuild" -> "Verify visually";
"Remove dev tag" -> "Complete";
}
| Step | Command/Action | Example |
|---|---|---|
| Generate tag | uuidgen | tr '[:upper:]' '[:lower:]' | cut -c1-8 |
dev-3f1b16a2 |
| Add to view | .storybookTags("dev-{uuid}") |
.storybookTags("dev-3f1b16a2") |
| Launch | Enable --storybook argument and run |
Xcode Run ▶ |
| Navigate | Search #{tag} in Storybook |
#dev-3f1b16a2 |
| Verify | View opens automatically if unique | Visual inspection |
| Clean up | Remove dev tag before commit | Delete .storybookTags() line |
Scenario: Building a new profile card component
// 1. Create the view
struct ProfileCard: View {
let name: String
let avatar: String
var body: some View {
HStack {
Image(systemName: avatar)
.font(.largeTitle)
Text(name)
.font(.headline)
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
}
// 2. Add to Storybook with unique dev tag
extension Storybook {
@objc static let profileCard = StorybookPage(
folder: "/Components/Profile",
views: [
ProfileCard(name: "John Doe", avatar: "person.circle")
.storybookTitle("Default State")
.storybookTags("dev-a7c8f3e1"), // ← Unique tag
ProfileCard(name: "Jane Smith", avatar: "person.circle.fill")
.storybookTitle("Filled Avatar")
.storybookTags("profile-card") // ← Semantic tag (stays)
]
)
}
// 3. Launch app with --storybook argument
// 4. Search: #dev-a7c8f3e1
// 5. View opens automatically
// 6. Iterate: make changes → hot reload → verify
// 7. Before commit: remove dev-a7c8f3e1 tag, keep profile-card tag
dev-{short-uuid}button, primary-cta, error-state)// Good: Both temporary and permanent tags
.storybookTags("dev-3f1b16a2", "button", "primary-cta")
// During dev: search #dev-3f1b16a2 (unique, auto-navigates)
// After dev: search #button or #primary-cta (find related components)
.storybookTags("button") // Too many results, no auto-navigation
Fix: Use UUID-based tag for development work
.storybookTags("dev-a7c8f3e1") // ← Still in code after commit
Fix: Always clean up dev tags before committing. Semantic tags should stay.
Running full app → navigating to test → finding Storybook
Fix: Configure --storybook argument to launch directly
Using dev-current for multiple views across sessions
Fix: Generate fresh UUID for each new development task
When implementing UI changes:
tag=$(uuidgen | tr '[:upper:]' '[:lower:]' | cut -c1-8).storybookTags("dev-$tag")--storybook#dev-$tag to verifyDuring development:
Before commit:
dev- tagsView doesn't auto-open:
# prefixCan't find view:
# to see all matchesLaunch argument not working:
--storybook argumentProcessInfo.processInfo.arguments