From swiftui-dev
Explains SwiftUI data flow with @State, @Binding, @Observable, @Bindable, and environment values. Useful for managing state, bindings, and system values in views.
npx claudepluginhub arustydev/agents --plugin swiftui-devThis skill uses the workspace's default tool permissions.
Understanding SwiftUI's data flow mechanisms including @Observable, @State, @Binding, and environment values.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Understanding SwiftUI's data flow mechanisms including @Observable, @State, @Binding, and environment values.
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}
struct ToggleRow: View {
let title: String
@Binding var isOn: Bool
var body: some View {
Toggle(title, isOn: $isOn)
}
}
struct SettingsView: View {
@State private var notificationsEnabled = false
var body: some View {
Form {
ToggleRow(title: "Notifications", isOn: $notificationsEnabled)
}
}
}
@Observable
final class UserSettings {
var username = ""
var notificationsEnabled = true
var theme: Theme = .system
// Computed properties work automatically
var isValid: Bool {
!username.isEmpty
}
}
struct SettingsView: View {
@State private var settings = UserSettings()
var body: some View {
Form {
TextField("Username", text: $settings.username)
Toggle("Notifications", isOn: $settings.notificationsEnabled)
Picker("Theme", selection: $settings.theme) {
ForEach(Theme.allCases) { theme in
Text(theme.rawValue).tag(theme)
}
}
}
.disabled(!settings.isValid)
}
}
struct EditUserView: View {
@Bindable var user: User // User is @Observable
var body: some View {
Form {
TextField("Name", text: $user.name)
TextField("Email", text: $user.email)
}
}
}
struct AdaptiveView: View {
@Environment(\.colorScheme) private var colorScheme
@Environment(\.horizontalSizeClass) private var sizeClass
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack {
Text("Theme: \(colorScheme == .dark ? "Dark" : "Light")")
Button("Close") {
dismiss()
}
}
}
}
// Define the key
private struct APIClientKey: EnvironmentKey {
static let defaultValue: APIClient = .live
}
// Extend EnvironmentValues
extension EnvironmentValues {
var apiClient: APIClient {
get { self[APIClientKey.self] }
set { self[APIClientKey.self] = newValue }
}
}
// Usage
struct DataView: View {
@Environment(\.apiClient) private var api
var body: some View {
// Use api
}
}
// Injection
MyApp()
.environment(\.apiClient, .mock)
@Observable
final class AppState {
var currentUser: User?
var isAuthenticated: Bool { currentUser != nil }
}
// In App
@main
struct MyApp: App {
@State private var appState = AppState()
var body: some Scene {
WindowGroup {
ContentView()
.environment(appState)
}
}
}
// In View
struct ProfileView: View {
@Environment(AppState.self) private var appState
var body: some View {
if let user = appState.currentUser {
Text("Hello, \(user.name)")
}
}
}
struct ParentView: View {
@State private var data = [Item]()
var body: some View {
List(data) { item in
ChildRow(item: item) // Pass as value
}
}
}
struct FilterView: View {
@Binding var selectedFilter: Filter
var body: some View {
Picker("Filter", selection: $selectedFilter) {
ForEach(Filter.allCases) { filter in
Text(filter.title).tag(filter)
}
}
}
}
struct ItemRow: View {
let item: Item
let onDelete: () -> Void
var body: some View {
HStack {
Text(item.name)
Spacer()
Button("Delete", action: onDelete)
}
}
}
@Observable
final class ShoppingCart {
var items: [CartItem] = []
var total: Decimal {
items.reduce(0) { $0 + $1.price * Decimal($1.quantity) }
}
func add(_ product: Product) {
if let index = items.firstIndex(where: { $0.productId == product.id }) {
items[index].quantity += 1
} else {
items.append(CartItem(product: product))
}
}
}
// Shared across views via environment
struct ProductView: View {
@Environment(ShoppingCart.self) private var cart
let product: Product
var body: some View {
Button("Add to Cart") {
cart.add(product)
}
}
}
// OLD (pre-iOS 17)
class ViewModel: ObservableObject {
@Published var items: [Item] = []
}
struct MyView: View {
@StateObject private var viewModel = ViewModel()
}
// NEW (iOS 17+)
@Observable
final class ViewModel {
var items: [Item] = []
}
struct MyView: View {
@State private var viewModel = ViewModel()
}