Skip to main content

swiftui-patterns-developer

SwiftUI view structure, composition, and best practices. Use when refactoring SwiftUI views, organizing view files, or extracting subviews.

Informações da origem

Repositório
anyproto/anytype-swift
Última atividade na origem
26 de janeiro de 2026 às 15:21
Idioma detectado do SKILL.md
inglês
Estrelas
523
Forks
78

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
swiftui-patterns-developer
description
SwiftUI view structure, composition, and best practices. Use when refactoring SwiftUI views, organizing view files, or extracting subviews.
# SwiftUI Patterns Developer (Smart Router) ## Purpose Apply consistent structure and patterns to SwiftUI views, with focus on ordering, subview extraction, and proper composition. ## When Auto-Activated - Refactoring SwiftUI view structure - Organizing view file layout - Splitting large views into subviews - Keywords: view structure, view ordering, split view, extract subview, large view, refactor view ## Core Guidelines ### 0) Three Qualities of SwiftUI Views (WWDC24) Understanding these fundamentals helps you write better SwiftUI code: **1. Declarative** - Describe what you want, not how to create it: ```swift // ✅ Declarative - describe the result List(pets) { pet in HStack { Text(pet.name) Spacer() Text(pet.species) } } // No need to add/remove rows manually - SwiftUI handles it ``` **2. Compositional** - Build complex UIs from simple building blocks: ```swift // ViewBuilder closures define children of containers HStack { // Container view Image(...) // Child 1 VStack { // Child 2 (also a container) Text(...) // Nested child Text(...) // Nested child } Spacer() // Child 3 } ``` **3. State-Driven** - UI automatically updates when state changes: ```swift // SwiftUI tracks dependencies and updates views automatically @State private var count = 0 var body: some View { Button("Count: \(count)") { // Dependency on `count` count += 1 // State change triggers re-render } } ``` **Key insight**: Views are VALUE TYPES (structs), not long-lived objects. They are descriptions of current UI state, not objects that receive commands over time. SwiftUI maintains the actual UI behind the scenes. ### 1) View Ordering (top -> bottom) Follow Anytype's property organization from IOS_DEVELOPMENT_GUIDE.md: ```swift struct ExampleView: View { // 1. Property wrappers (@State, @Injected, @Environment) @State private var model: ExampleViewModel @Injected(\.settingsService) private var settingsService @Environment(\.dismiss) private var dismiss // 2. Public properties (let/var) let title: String // 3. Private properties private var cancellables = Set<AnyCancellable>() // 4. Computed properties private var hasItems: Bool { !model.items.isEmpty } // 5. init (if needed) init(title: String) { self.title = title _model = State(wrappedValue: ExampleViewModel(title: title)) } // 6. body var body: some View { content .task { await model.startSubscriptions() } } // 7. Computed view builders private var content: some View { ... } // 8. Helper / async functions private func handleTap() { ... } } ``` ### 2) ViewModel Pattern (Anytype Standard) Anytype uses **MVVM with ViewModels**. Always use ViewModels for business logic: ```swift // View - lightweight, UI only struct ChatView: View { @State private var model: ChatViewModel init(spaceId: String, chatId: String) { _model = State(wrappedValue: ChatViewModel(spaceId: spaceId, chatId: chatId)) } var body: some View { content .task { await model.startSubscriptions() } } private var content: some View { List(model.messages) { message in MessageRow(message: message) } } } // ViewModel - handles business logic @MainActor @Observable final class ChatViewModel { var messages: [Message] = [] @ObservationIgnored @Injected(\.chatService) private var chatService func startSubscriptions() async { // Heavy work here, not in init } func sendMessage(_ text: String) async { // Business logic } } ``` **Key points:** - Use `@State private var model: ViewModel` in views - Initialize ViewModel in view's `init` with `_model = State(wrappedValue:)` - Keep ViewModel init cheap, heavy work in `.task` - Use `@Observable` macro (not `ObservableObject`) - Mark ViewModels with `@MainActor` ### 3) How Observation Works (WWDC23) Understanding **why** `@Observable` works helps you use it correctly. **Property Access Tracking:** - SwiftUI tracks which properties you **access** during `body` evaluation - Only those accessed properties trigger view invalidation when changed - Properties NOT read in `body` don't cause re-renders (unlike `@Published`) ```swift @Observable final class SettingsViewModel { var userName: String = "" // Accessed in body → triggers update var isLoading: Bool = false // Accessed in body → triggers update var analyticsData: Data = Data() // NOT accessed in body → no update } struct SettingsView: View { @State private var model: SettingsViewModel var body: some View { // SwiftUI tracks: "this view reads userName and isLoading" VStack { Text(model.userName) // ✓ Tracked if model.isLoading { // ✓ Tracked ProgressView() } // model.analyticsData not read → changes won't invalidate this view } } } ``` **Per-Instance Tracking:** - Arrays of `@Observable` objects work efficiently - Only the specific instance that changed triggers updates - No need for `identifiable` tricks with observation ```swift @Observable final class MessageViewModel { var text: String var isRead: Bool = false } // Each MessageRow only updates when ITS message changes List(model.messages) { message in MessageRow(message: message) // Only this row updates when message.isRead changes } ``` **Computed Properties Just Work:** - Computed properties composed from stored properties are automatically tracked - SwiftUI traces through to the underlying stored properties ```swift @Observable final class CartViewModel { var items: [Item] = [] var discount: Double = 0 // Computed → tracks both `items` and `discount` var totalPrice: Double { items.reduce(0) { $0 + $1.price } - discount } } ``` **Performance Benefit:** With `@Observable`, views only update when properties they actually read change. This is more efficient than `ObservableObject` where ANY `@Published` change triggers `objectWillChange` for ALL subscribers. ### 4) Property Wrapper Decision Tree When to use which wrapper with `@Observable`: | Scenario | Wrapper | Why | |----------|---------|-----| | View **owns** model lifecycle | `@State` | View creates and manages the model | | Model shared **app-wide** | `@Environment` | Injected at app root, read anywhere | | Just need **bindings** ($syntax) | `@Bindable` | Pass to TextField, Toggle, etc. | | Just **reading** the model | Nothing | Direct property access triggers tracking | ```swift // View OWNS the model (creates it) struct ChatView: View { @State private var model: ChatViewModel // ← @State init(chatId: String) { _model = State(wrappedValue: ChatViewModel(chatId: chatId)) } } // Model passed from parent, need bindings struct MessageEditor: View { @Bindable var draft: DraftMessage // ← @Bindable for $draft.text var body: some View { TextField("Message", text: $draft.text) } } // Just reading, no bindings needed struct MessageRow: View { let message: MessageViewModel // ← Nothing! Just read properties var body: some View { Text(message.text) Image(systemName: message.isRead ? "checkmark.circle.fill" : "circle") } } ``` **Migration from ObservableObject:** | Old | New | |-----|-----| | `@StateObject` | `@State` | | `@ObservedObject` | `@Bindable` or nothing | | `@EnvironmentObject` | `@Environment` | ### 5) Migration from ObservableObject (WWDC23) Step-by-step conversion from legacy `ObservableObject`: **Before (ObservableObject):** ```swift class SettingsViewModel: ObservableObject { @Published var userName: String = "" @Published var notifications: Bool = true private var cancellables = Set<AnyCancellable>() } struct SettingsView: View { @StateObject private var model = SettingsViewModel() var body: some View { TextField("Name", text: $model.userName) Toggle("Notifications", isOn: $model.notifications) } } ``` **After (@Observable):** ```swift @Observable final class SettingsViewModel { var userName: String = "" var notifications: Bool = true @ObservationIgnored private var cancellables = Set<AnyCancellable>() } struct SettingsView: View { @State private var model = SettingsViewModel() var body: some View { @Bindable var model = model // Local binding for $ syntax TextField("Name", text: $model.userName) Toggle("Notifications", isOn: $model.notifications) } } ``` **Migration Steps:** 1. Remove `ObservableObject` conformance, add `@Observable` macro 2. Remove `@Published` from all properties (observation is automatic) 3. Add `@ObservationIgnored` to properties that shouldn't trigger updates 4. Change `@StateObject` → `@State` in views 5. For `$` binding syntax, use `@Bindable var model = model` in body 6. Replace `@EnvironmentObject` with `@Environment` **Note:** Anytype already uses `@Observable` - this section is for understanding legacy code during migrations. ### 6) Dependency Injection (Factory) Anytype uses **Factory DI**, not SwiftUI Environment for services: ```swift // ✅ CORRECT - Factory DI @Injected(\.chatService) private var chatService // ❌ WRONG - Environment for services @Environment(ChatService.self) private var chatService ``` **Environment is for:** - System values: `@Environment(\.dismiss)`, `@Environment(\.colorScheme)` - SwiftUI-provided context **@Injected is for:** - App services: `@Injected(\.chatService)` - Repositories: `@Injected(\.userRepository)` - Any business logic dependencies ### 7) View Modifiers and Order (WWDC24) View modifiers create a hierarchical structure. **Order matters** - modifiers are applied sequentially: ```swift // Each modifier wraps the previous result Image("whiskers") .clipShape(Circle()) // 1. Clip to circle first .shadow(radius: 4) // 2. Add shadow to clipped shape .overlay( // 3. Overlay on top of shadow Circle().stroke(.green, lineWidth: 2) ) ``` The hierarchy and order of effect is defined by the exact order of modifiers. Chaining modifiers makes it clear how results are produced and how to customize them. ### 8) Adaptive Views (WWDC24) SwiftUI views describe **purpose**, not exact visual construction. This enables adaptation: **Buttons** - Same purpose (labeled action), different contexts: ```swift // Adapts to: borderless, bordered, prominent styles // Adapts to: swipe actions, menus, forms Button("Edit", action: handleEdit) // In swipe actions .swipeActions { Button("Delete", role: .destructive) { delete() } Button("Archive") { archive() } } ``` **Toggles** - Switch, checkbox, or toggle button depending on context: ```swift // Automatically shows appropriate style for platform/context Toggle("Notifications", isOn: $notificationsEnabled) ``` **Searchable** - Describes capability, SwiftUI handles idiomatic presentation: ```swift // iOS: overlay list, macOS: dropdown menu List(filteredItems) { ... } .searchable(text: $searchText) .searchSuggestions { ForEach(suggestions) { Text($0) } } ``` ### 9) Split Large Bodies If `body` grows beyond a screen, split into smaller subviews: ```swift // Computed view properties (same file) var body: some View { List { header filters results } } private var header: some View { ... } private var filters: some View { ... } private var results: some View { ... } ``` ```swift // Extracted subview (reusable or complex) struct HeaderSection: View { let title: String let subtitle: String? var body: some View { VStack(alignment: .leading, spacing: 4) { AnytypeText(title, style: .heading) if let subtitle { AnytypeText(subtitle, style: .bodyRegular) } } } } ``` ### 8) ViewState Enum Pattern For views with loading/error/loaded states: ```swift enum ViewState { case loading case error(String) case loaded } @MainActor @Observable final class FeedViewModel { var viewState: ViewState = .loading var posts: [Post] = [] func loadPosts() async { do { posts = try await feedService.getFeed() viewState = .loaded } catch { viewState = .error(error.localizedDescription) } } } struct FeedView: View { @State private var model: FeedViewModel var body: some View { content .task { await model.loadPosts() } } @ViewBuilder private var content: some View { switch model.viewState { case .loading: ProgressView() case .error(let message): ErrorView(message: message, retry: { Task { await model.loadPosts() } }) case .loaded: List(model.posts) { post in PostRow(post: post) } } } } ``` ### 11) State, Binding, and Source of Truth (WWDC24) **@State** creates internal source of data for a view: ```swift struct RatingView: View { @State private var rating = 0 // View owns this state var body: some View { HStack { Text("\(rating)")
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub