Master SwiftUI - Declarative UI, state management, animations, modern iOS development
Build modern iOS interfaces using SwiftUI's declarative syntax. Claude will generate SwiftUI views with proper state management (@State, @Binding, @Observable) when you request iOS UI components or ask to refactor UIKit code to SwiftUI.
/plugin marketplace add pluginagentmarketplace/custom-plugin-ios/plugin install ios-assistant@pluginagentmarketplace-iosThis skill inherits all available tools. When active, it can use any tool Claude has access to.
assets/config.yamlassets/schema.jsonreferences/GUIDE.mdreferences/PATTERNS.mdscripts/validate.pyBuild modern, declarative iOS interfaces with SwiftUI
By completing this skill, you will:
| Requirement | Level |
|---|---|
| iOS Fundamentals | Completed |
| Swift | Intermediate |
| Functional programming concepts | Basic |
Topics:
Code Examples:
// Basic view composition
struct ProfileView: View {
let user: User
var body: some View {
VStack(spacing: 16) {
ProfileImage(url: user.avatarURL)
Text(user.name)
.font(.title)
.fontWeight(.bold)
Text(user.bio)
.font(.body)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
}
.padding()
}
}
// Custom container with ViewBuilder
struct Card<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
content
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(radius: 4)
}
}
Checkpoint: Build profile card component
Topics:
State Hierarchy:
@State → Local, value type
@Binding → Pass to child, two-way
@StateObject → Own the object, create once
@ObservedObject → Don't own, receive from parent
@EnvironmentObject → Shared globally
@Observable → Modern replacement (iOS 17+)
Modern Approach (iOS 17+):
@Observable
final class UserViewModel {
var user: User?
var isLoading = false
var error: Error?
private let service: UserServiceProtocol
init(service: UserServiceProtocol = UserService()) {
self.service = service
}
func loadUser() async {
isLoading = true
defer { isLoading = false }
do {
user = try await service.fetchCurrentUser()
} catch {
self.error = error
}
}
}
struct UserView: View {
@State private var viewModel = UserViewModel()
var body: some View {
Group {
if viewModel.isLoading {
ProgressView()
} else if let user = viewModel.user {
UserContent(user: user)
}
}
.task {
await viewModel.loadUser()
}
}
}
Checkpoint: Build stateful form with validation
Topics:
Navigation Stack:
enum Route: Hashable {
case profile(userId: String)
case settings
case detail(item: Item)
}
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
HomeView(navigate: navigate)
.navigationDestination(for: Route.self) { route in
switch route {
case .profile(let userId):
ProfileView(userId: userId)
case .settings:
SettingsView()
case .detail(let item):
DetailView(item: item)
}
}
}
}
private func navigate(to route: Route) {
path.append(route)
}
private func popToRoot() {
path.removeLast(path.count)
}
}
Checkpoint: Build multi-screen navigation flow
Topics:
Grid Layout:
struct PhotoGrid: View {
let photos: [Photo]
private let columns = [
GridItem(.adaptive(minimum: 100, maximum: 150), spacing: 8)
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 8) {
ForEach(photos) { photo in
AsyncImage(url: photo.thumbnailURL) { phase in
switch phase {
case .empty:
Rectangle()
.fill(.quaternary)
case .success(let image):
image
.resizable()
.scaledToFill()
case .failure:
Image(systemName: "photo")
@unknown default:
EmptyView()
}
}
.frame(height: 100)
.clipShape(RoundedRectangle(cornerRadius: 8))
}
}
.padding()
}
}
}
Checkpoint: Build searchable photo grid
Topics:
Animation Examples:
// Implicit animation
struct ExpandableCard: View {
@State private var isExpanded = false
var body: some View {
VStack {
Text("Title")
.font(.headline)
if isExpanded {
Text("Detailed content here...")
.transition(.opacity.combined(with: .move(edge: .top)))
}
}
.frame(maxWidth: .infinity)
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 12))
.onTapGesture {
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
isExpanded.toggle()
}
}
}
}
// Matched geometry effect
struct CardTransition: View {
@Namespace private var animation
@State private var selectedCard: Card?
var body: some View {
ZStack {
if let card = selectedCard {
DetailView(card: card)
.matchedGeometryEffect(id: card.id, in: animation)
.onTapGesture { selectedCard = nil }
} else {
LazyVGrid(columns: columns) {
ForEach(cards) { card in
CardView(card: card)
.matchedGeometryEffect(id: card.id, in: animation)
.onTapGesture { selectedCard = card }
}
}
}
}
.animation(.spring(response: 0.5), value: selectedCard)
}
}
Checkpoint: Create hero animation transition
Topics:
Custom Modifier:
struct CardStyle: ViewModifier {
let isHighlighted: Bool
func body(content: Content) -> some View {
content
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(
color: isHighlighted ? .accentColor.opacity(0.3) : .black.opacity(0.1),
radius: isHighlighted ? 8 : 4
)
.overlay {
if isHighlighted {
RoundedRectangle(cornerRadius: 12)
.stroke(.accent, lineWidth: 2)
}
}
}
}
extension View {
func cardStyle(isHighlighted: Bool = false) -> some View {
modifier(CardStyle(isHighlighted: isHighlighted))
}
}
// Usage
Text("Content")
.cardStyle(isHighlighted: true)
Checkpoint: Build custom button style library
| Criteria | Weight |
|---|---|
| View composition | 25% |
| State management | 30% |
| Navigation | 20% |
| Animation quality | 15% |
| Code organization | 10% |
// Debug view updates
var body: some View {
let _ = Self._printChanges()
// Your view...
}
// Debug layout
view.border(.red) // Visual bounds
Complete these projects:
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.