ui-patterns
2
总安装量
2
周安装量
#70329
全站排名
安装命令
npx skills add https://github.com/patrickserrano/skills --skill ui-patterns
Agent 安装分布
amp
2
github-copilot
2
codex
2
kimi-cli
2
gemini-cli
2
opencode
2
Skill 文档
SwiftUI UI Patterns
Quick Start
Existing Project
- Identify the feature and primary interaction model (list, detail, editor, settings, tabbed)
- Find nearby examples in the repo with
rg "TabView\("or similar - Apply local conventions: prefer SwiftUI-native state, keep state local
- Build with small, focused subviews and SwiftUI-native data flow
New Project Scaffolding
- Wire TabView + NavigationStack + sheets
- Add minimal
AppTabenum andRouterPath - Expand route and sheet enums as new screens are added
General Rules
- Use modern SwiftUI state (
@State,@Binding,@Observable,@Environment) - Avoid unnecessary view models – prefer MV (Model-View) over MVVM
- Prefer composition; keep views small and focused
- Use async/await with
.taskand explicit loading/error states - Follow the project’s formatter and style guide
Sheets Best Practices
- Prefer
.sheet(item:)over.sheet(isPresented:)when state represents a selected model - Avoid
if letinside a sheet body - Sheets should own their actions and call
dismiss()internally
Workflow for a New SwiftUI View
- Define the view’s state and its ownership location
- Identify dependencies to inject via
@Environment - Sketch the view hierarchy and extract repeated parts into subviews
- Implement async loading with
.taskand explicit state enum if needed - Add accessibility labels or identifiers for interactive UI
- Validate with a build and update usage callsites if needed
MV Pattern (Preferred over MVVM)
SwiftUI views should be lightweight state expressions. Avoid ViewModels unless truly necessary.
struct FeedView: View {
@Environment(APIClient.self) private var client
enum ViewState {
case loading
case error(String)
case loaded([Post])
}
@State private var viewState: ViewState = .loading
var body: some View {
NavigationStack {
List {
switch viewState {
case .loading:
ProgressView("Loading...")
case .error(let message):
ErrorView(message: message, retry: { await loadFeed() })
case .loaded(let posts):
ForEach(posts) { post in
PostRow(post: post)
}
}
}
.task { await loadFeed() }
}
}
private func loadFeed() async {
do {
let posts = try await client.getFeed()
viewState = .loaded(posts)
} catch {
viewState = .error(error.localizedDescription)
}
}
}
Sheet Patterns
Item-driven sheet (preferred)
@State private var selectedItem: Item?
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
}
Sheet owns its actions
struct EditItemSheet: View {
@Environment(\.dismiss) private var dismiss
@Environment(Store.self) private var store
let item: Item
@State private var isSaving = false
var body: some View {
VStack {
Button(isSaving ? "Saving..." : "Save") {
Task { await save() }
}
}
}
private func save() async {
isSaving = true
await store.save(item)
dismiss()
}
}
App-Level Environment Setup
@main
struct MyApp: App {
@State var client: APIClient = .init()
@State var router: AppRouter = .init()
var body: some Scene {
WindowGroup {
TabView(selection: $router.selectedTab) {
ForEach(AppTab.allCases) { tab in
tab.rootView
.tag(tab)
}
}
.environment(client)
.environment(router)
}
}
}
State Management Guidelines
| Wrapper | Use Case |
|---|---|
@State |
Local, ephemeral view state |
@Binding |
Two-way data flow from parent |
@Observable |
Shared state across views (iOS 17+) |
@Environment |
Dependency injection, app-wide concerns |
@Query |
SwiftData queries directly in views |
Task and onChange Patterns
// React to state changes
.task(id: searchText) {
guard !searchText.isEmpty else { return }
await search(query: searchText)
}
// Respond to state transitions
.onChange(of: isActive, initial: false) {
guard isActive else { return }
Task { await refresh() }
}
Why Not MVVM?
SwiftUI was designed without ViewModels in mind:
- Views are structs, lightweight and disposable
@State,@Environment,@Observablehandle all data flow needs- ViewModels add complexity, indirection, and cognitive overhead
- SwiftData’s
@Queryworks directly in views
Instead:
- Keep views as pure expressions of state
- Put business logic in services/models injected via
@Environment - Test services and models, not views
- Use SwiftUI Previews for visual regression testing