| name | swift-swiftui |
| description | Build modern UIs with SwiftUI - views, state management, animations, navigation |
| version | 2.0.0 |
| sasmp_version | 1.3.0 |
| bonded_agent | 03-swift-swiftui |
| bond_type | PRIMARY_BOND |
SwiftUI Skill
Declarative UI framework knowledge for building modern Apple platform interfaces.
Prerequisites
- Xcode 15+ installed
- iOS 16+ / macOS 13+ deployment target recommended
- Understanding of reactive programming concepts
Parameters
parameters:
min_ios_version:
type: string
default: "16.0"
description: Minimum iOS version
platforms:
type: array
items: [iOS, macOS, watchOS, tvOS, visionOS]
default: [iOS]
observation_framework:
type: string
enum: [observation, combine, observable_object]
default: observation
description: State management approach
Topics Covered
Property Wrappers
| Wrapper | Ownership | Use Case |
|---|
@State | View owns | Local, private state |
@Binding | Parent owns | Two-way child connection |
@StateObject | View creates/owns | Observable object lifecycle |
@ObservedObject | External owns | Passed observable |
@EnvironmentObject | Environment owns | Dependency injection |
@Environment | System provides | System values (colorScheme, etc) |
Observation (iOS 17+)
| Feature | Description |
|---|
@Observable | Macro for observable classes |
@Bindable | Create bindings from Observable |
| Automatic tracking | No need for @Published |
Layout System
| Container | Purpose |
|---|
VStack | Vertical arrangement |
HStack | Horizontal arrangement |
ZStack | Overlapping views |
LazyVStack/HStack | Lazy loading for lists |
Grid | 2D grid layout |
GeometryReader | Access to size/position |
Code Examples
Observation Pattern (iOS 17+)
import SwiftUI
@Observable
final class ShoppingCart {
var items: [CartItem] = []
var couponCode: String = ""
var subtotal: Decimal {
items.reduce(0) { $0 + $1.price * Decimal($1.quantity) }
}
var total: Decimal {
let discount = applyCoupon(to: subtotal)
return subtotal - discount
}
func add(_ product: Product, quantity: Int = 1) {
if let index = items.firstIndex(where: { $0.product.id == product.id }) {
items[index].quantity += quantity
} else {
items.append(CartItem(product: product, quantity: quantity))
}
}
func remove(_ item: CartItem) {
items.removeAll { $0.id == item.id }
}
( : ) -> {
couponCode.isEmpty { }
amount
}
}
: {
cart:
body: {
{
(cart.items) { item
(item: item)
}
.onDelete { indexSet
cart.items.remove(atOffsets: indexSet)
}
{
{
(, text: .couponCode)
() { }
}
(, value: cart.subtotal, format: .currency(code: ))
(, value: cart.total, format: .currency(code: ))
.fontWeight(.bold)
}
}
.navigationTitle()
}
}
Custom View Modifier
struct CardStyle: ViewModifier {
let cornerRadius: CGFloat
let shadowRadius: CGFloat
func body(content: Content) -> some View {
content
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
.shadow(color: .black.opacity(0.1), radius: shadowRadius, y: 2)
}
}
extension View {
func cardStyle(cornerRadius: CGFloat = 12, shadowRadius: CGFloat = 4) -> some View {
modifier(CardStyle(cornerRadius: cornerRadius, shadowRadius: shadowRadius))
}
}
struct ProductCard: View {
let product: Product
var body: some View {
VStack(alignment: .leading, spacing: 8) {
AsyncImage(url: product.imageURL) { image in
image.resizable().aspectRatio(contentMode: .fill)
} placeholder: {
ProgressView()
}
.frame(height: 150)
.clipped()
(product.name)
.font(.headline)
(product.price, format: .currency(code: ))
.foregroundStyle(.secondary)
}
.cardStyle()
}
}
Custom Animations
struct PulsingButton: View {
let title: String
let action: () -> Void
@State private var isPulsing = false
var body: some View {
Button(action: action) {
Text(title)
.font(.headline)
.foregroundStyle(.white)
.padding(.horizontal, 24)
.padding(.vertical, 12)
.background(.blue)
.clipShape(Capsule())
.scaleEffect(isPulsing ? 1.05 : 1.0)
}
.onAppear {
withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) {
isPulsing = true
}
}
}
}
struct MatchedGeometryExample: View {
@Namespace private var animation
@State private var isExpanded = false
var body: some View {
VStack {
if isExpanded {
RoundedRectangle(cornerRadius: 20)
.fill(.blue)
.matchedGeometryEffect(id: , in: animation)
.frame(height: )
} {
(cornerRadius: )
.fill(.blue)
.matchedGeometryEffect(id: , in: animation)
.frame(width: , height: )
}
}
.onTapGesture {
withAnimation(.spring(response: , dampingFraction: )) {
isExpanded.toggle()
}
}
}
}
Navigation Stack (iOS 16+)
struct NavigationExample: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List(products) { product in
NavigationLink(value: product) {
ProductRow(product: product)
}
}
.navigationTitle("Products")
.navigationDestination(for: Product.self) { product in
ProductDetailView(product: product)
}
.navigationDestination(for: Category.self) { category in
CategoryView(category: category)
}
}
}
func navigateToProduct(_ product: Product) {
path.append(product)
}
func popToRoot() {
path.removeLast(path.count)
}
}
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|
| View not updating | Wrong property wrapper | Check ownership: @State vs @StateObject |
| Preview crash | Missing mock data | Provide preview with sample data |
| Animation stutters | Expensive body | Extract subviews, avoid complex calculations |
| Navigation broken | Missing NavigationStack | Ensure view is inside NavigationStack |
| List slow | Complex cells | Use LazyVStack, simplify cell views |
Debug Tips
var body: some View {
let _ = Self._printChanges()
}
#if DEBUG
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView(data: .preview)
}
}
#endif
Validation Rules
validation:
- rule: state_ownership
severity: error
check: @StateObject for views that create, @ObservedObject for passed
- rule: body_purity
severity: warning
check: No side effects in body computed property
- rule: lazy_for_lists
severity: info
check: Use LazyVStack/LazyHStack for long scrolling content
Usage
Skill("swift-swiftui")
Related Skills
swift-combine - Reactive programming
swift-uikit - UIKit interop
swift-architecture - MVVM patterns