Token导航 LogoToken导航TokenDH.com
研究检索权限需确认github未标认证来源可访问clear审计通过

swiftui-patternsSwiftUI 模式

Agent Skill

swiftui-patterns 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

326

周安装

14

GitHub Stars

8

下载量

114
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:swiftui-patterns(SwiftUI 模式)
来源仓库:https://github.com/kaakati/rails-enterprise-dev
仓库路径:skills/swiftui-patterns
安装命令:
npx skills add https://github.com/kaakati/rails-enterprise-dev --skill swiftui-patterns
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/kaakati/rails-enterprise-dev --skill swiftui-patterns

简介

检索 SwiftUI 界面组件最佳实践和状态管理方案。

  • 适用于 Codex、Claude、Cursor、Gemini CLI 中的移动端 UI 开发。
  • 通过 GitHub 仓库安装,使用 npx skills add 命令添加技能。
  • 跨平台适配需验证 iOS 各版本兼容性。
  • 推荐模式应结合 Apple Human Interface Guidelines 使用。

SKILL.md

SwiftUI Patterns — Expert Decisions

Expert decision frameworks for SwiftUI choices that require experience. Claude knows SwiftUI syntax — this skill provides the judgment calls that prevent subtle bugs.


Decision Trees

Property Wrapper Selection

Who creates the object?
├─ This view creates it
│  └─ Is it a value type (struct, primitive)?
│     ├─ YES → @State
│     └─ NO (class/ObservableObject)
│        └─ iOS 17+?
│           ├─ YES → @Observable class + var (no wrapper)
│           └─ NO → @StateObject
│
└─ Parent passes it down
   └─ Is it an ObservableObject?
      ├─ YES → @ObservedObject
      └─ NO
         └─ Need two-way binding?
            ├─ YES → @Binding
            └─ NO → Regular parameter

The @StateObject vs @ObservedObject trap: Using @ObservedObject for a locally-created object causes recreation on EVERY view update. State vanishes randomly.

// ❌ BROKEN — viewModel recreated on parent rerender
struct BadView: View {
    @ObservedObject var viewModel = UserViewModel()  // WRONG
}

// ✅ CORRECT — viewModel survives view updates
struct GoodView: View {
    @StateObject private var viewModel = UserViewModel()
}

Navigation Pattern Selection

How many columns needed?
├─ 1 column (stack-based)
│  └─ NavigationStack with .navigationDestination
│
├─ 2 columns (list → detail)
│  └─ NavigationSplitView (2 column)
│     └─ iPad: sidebar + detail
│     └─ iPhone: collapses to stack
│
└─ 3 columns (sidebar → list → detail)
   └─ NavigationSplitView (3 column)
      └─ Mail/Files app pattern

NavigationStack gotcha: navigationDestination(for:) must be attached to a view INSIDE the NavigationStack, not on the NavigationStack itself. Wrong placement = silent failure.

// ❌ WRONG — destination outside stack hierarchy
NavigationStack {
    ContentView()
}
.navigationDestination(for: Item.self) { ... } // Never triggers!

// ✅ CORRECT — destination inside stack
NavigationStack {
    ContentView()
        .navigationDestination(for: Item.self) { item in
            DetailView(item: item)
        }
}

Sheet vs FullScreenCover vs NavigationLink

Is it a modal workflow? (user must complete or cancel)
├─ YES
│  └─ Should user see parent context?
│     ├─ YES → .sheet (can dismiss by swiping)
│     └─ NO → .fullScreenCover (must tap button)
│
└─ NO (progressive disclosure in same flow)
   └─ NavigationLink / .navigationDestination

Sheet state gotcha: Sheet content is created BEFORE presentation. @StateObject inside sheet reinitializes on each presentation.

// ❌ PROBLEM — viewModel resets each time sheet opens
.sheet(isPresented: $show) {
    SheetView()  // New @StateObject created each time
}

// ✅ SOLUTION — pass data or use item binding
.sheet(item: $selectedItem) { item in
    SheetView(item: item)  // Item drives content
}

NEVER Do

View Identity & Performance

NEVER use AnyView unless absolutely necessary:

// ❌ Destroys view identity — state lost, animations break
func makeView() -> AnyView {
    if condition {
        return AnyView(ViewA())
    } else {
        return AnyView(ViewB())
    }
}

// ✅ Use @ViewBuilder — preserves identity
@ViewBuilder
func makeView() -> some View {
    if condition {
        ViewA()
    } else {
        ViewB()
    }
}

NEVER compute expensive values in body:

// ❌ Recomputed on EVERY view update
var body: some View {
    let processed = expensiveComputation(data)  // Runs constantly
    Text(processed)
}

// ✅ Use .task or computed property with caching
var body: some View {
    Text(cachedResult)
        .task(id: data) {
            cachedResult = await expensiveComputation(data)
        }
}

NEVER change view identity during animation:

// ❌ Animation breaks — different views
if isExpanded {
    ExpandedCard()  // One view
} else {
    CompactCard()   // Different view
}

// ✅ Same view, different state — smooth animation
CardView(isExpanded: isExpanded)
    .animation(.spring(), value: isExpanded)

State Management

NEVER mutate @Published from background thread:

// ❌ Undefined behavior — sometimes works, sometimes crashes
Task.detached {
    viewModel.items = newItems  // Background thread!
}

// ✅ Always MainActor for @Published
Task { @MainActor in
    viewModel.items = newItems
}
// Or mark entire ViewModel as @MainActor

NEVER use.onAppear for async data loading:

// ❌ No cancellation, runs multiple times
.onAppear {
    Task { await loadData() }  // Not cancelled on disappear
}

// ✅ Use .task — automatic cancellation
.task {
    await loadData()  // Cancelled when view disappears
}

NEVER store derived state that should be computed:

// ❌ State duplication — can become inconsistent
@State private var items: [Item] = []
@State private var itemCount: Int = 0  // Derived from items!

// ✅ Compute derived values
@State private var items: [Item] = []
var itemCount: Int { items.count }

Lists & ForEach

NEVER use array index as id:

// ❌ Bugs when array changes — wrong rows update
ForEach(items.indices, id: \.self) { index in
    ItemRow(item: items[index])
}

// ✅ Use stable identifier
ForEach(items) { item in  // Requires Identifiable
    ItemRow(item: item)
}
// Or explicit id
ForEach(items, id: \.stableId) { item in ... }

NEVER put List inside ScrollView:

// ❌ Double scrolling, broken behavior
ScrollView {
    List(items) { ... }
}

// ✅ List handles its own scrolling
List(items) { item in
    ItemRow(item: item)
}

iOS/tvOS Platform Patterns

tvOS Focus System

#if os(tvOS)
struct TVCardView: View {
    @Environment(\.isFocused) var isFocused

    var body: some View {
        VStack {
            Image(item.image)
            Text(item.title)
        }
        .scaleEffect(isFocused ? 1.1 : 1.0)
        .animation(.easeInOut(duration: 0.15), value: isFocused)
        // tvOS: 10ft viewing distance = larger touch targets
        .frame(width: 300, height: 400)
    }
}

struct TVRowView: View {
    @FocusState private var focusedIndex: Int?

    var body: some View {
        ScrollView(.horizontal) {
            HStack(spacing: 48) {  // tvOS needs larger spacing
                ForEach(items.indices, id: \.self) { index in
                    TVCardView(item: items[index])
                        .focusable()
                        .focused($focusedIndex, equals: index)
                }
            }
            .padding(.horizontal, 90)  // Safe area for overscan
        }
        .onAppear { focusedIndex = 0 }
    }
}
#endif

tvOS gotcha: Focus system REQUIRES explicit .focusable() on custom views. Without it, remote navigation skips the view entirely.

Adaptive Layout Decision

struct AdaptiveView: View {
    @Environment(\.horizontalSizeClass) var sizeClass

    var body: some View {
        // iPhone portrait: compact, iPad/iPhone landscape: regular
        if sizeClass == .compact {
            VStack { content }
        } else {
            HStack { sidebar; content }
        }
    }
}

// Or use ViewThatFits for automatic selection
ViewThatFits {
    HStack { wideContent }  // Try first
    VStack { narrowContent } // Fallback
}

Performance Patterns

Preventing Unnecessary Redraws

// ✅ Equatable conformance for diffing
struct ItemRow: View, Equatable {
    let item: Item

    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.item.id == rhs.item.id &&
        lhs.item.name == rhs.item.name
    }

    var body: some View {
        Text(item.name)
    }
}

// ✅ Extract child views to isolate updates
struct ParentView: View {
    @StateObject var viewModel = ParentViewModel()

    var body: some View {
        VStack {
            // Only rerenders when header data changes
            HeaderView(title: viewModel.title)
            // Only rerenders when items change
            ItemList(items: viewModel.items)
        }
    }
}

Lazy Loading Patterns

// ✅ LazyVStack for large lists — views created on demand
ScrollView {
    LazyVStack {
        ForEach(items) { item in
            ItemRow(item: item)  // Created when scrolled into view
        }
    }
}

// ✅ task(id:) for dependent async work
.task(id: searchQuery) {
    // Automatically cancels previous task when searchQuery changes
    results = await search(searchQuery)
}

Common Gotchas

Sheet/Alert Binding Timing

// ❌ PROBLEM — item is nil when sheet renders
@State var selectedItem: Item?

.sheet(isPresented: Binding(
    get: { selectedItem != nil },
    set: { if !$0 { selectedItem = nil } }
)) {
    ItemDetail(item: selectedItem!)  // Crash! nil during transition
}

// ✅ SOLUTION — use item binding
.sheet(item: $selectedItem) { item in
    ItemDetail(item: item)  // item guaranteed non-nil
}

GeometryReader Sizing

// ❌ GeometryReader expands to fill available space
VStack {
    GeometryReader { geo in
        Text("Small text")  // But GeometryReader takes ALL space
    }
    Text("Never visible")  // Pushed off screen
}

// ✅ Wrap in fixed-size container or use sparingly
VStack {
    Text("Visible")
    Text("Also visible")
}
.background(
    GeometryReader { geo in
        Color.clear.onAppear { size = geo.size }
    }
)

Animation + State Change Timing

// ❌ State change THEN animation — no animation occurs
showDetail = true
withAnimation { }  // Nothing to animate

// ✅ State change INSIDE withAnimation
withAnimation(.spring()) {
    showDetail = true  // This change gets animated
}

Quick Reference

Property Wrapper Cheat Sheet

WrapperCreatesSurvives UpdateUse Case
@StateView-local value types
@StateObjectView-owned ObservableObject
@ObservedObjectParent-passed ObservableObject
@BindingN/ATwo-way value connection
@EnvironmentObjectN/AApp-wide shared state

Modifier Order Matters

// Different results!
Text("A").padding().background(.red)  // Red includes padding
Text("B").background(.red).padding()  // Red only behind text

Common order: content modifiers → padding → background → frame → position

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Antigravity

28.05%
按下载量换算32

Claude Code

21.11%
按下载量换算24

OpenCode

17.62%
按下载量换算20

Gemini CLI

11.43%
按下载量换算13

Codex

8.11%
按下载量换算9

windsurf

2.97%
按下载量换算3

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

权限需确认

当前来源未能明确判断权限范围,默认进入异常复核队列。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

来源信息

继续浏览同类 Skills