swiftui-agent-skill
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwiftUI Agent Skill
SwiftUI Agent Skill
Skill by ara.so — AI Agent Skills collection.
An agent skill that helps AI coding assistants write smarter, simpler, and more modern SwiftUI code. Provides guidance on API usage, design patterns, performance optimization, and accessibility. Covers navigation, layout, animations, state management, VoiceOver, deprecated APIs, and common LLM mistakes.
由ara.so开发的技能——AI Agent技能合集。
这是一款Agent技能,可帮助AI编码助手编写更智能、更简洁、更现代化的SwiftUI代码。提供API使用、设计模式、性能优化和无障碍访问方面的指导,涵盖导航、布局、动画、状态管理、VoiceOver、废弃API以及LLM常见错误等内容。
Installation
安装
Install via npx:
bash
npx skills add https://github.com/twostraws/swiftui-agent-skill --skill swiftui-proOr via Claude Code marketplace:
/plugin marketplace add twostraws/SwiftUI-Agent-Skill
/plugin install swiftui-pro@swiftui-agent-skill通过npx安装:
bash
npx skills add https://github.com/twostraws/swiftui-agent-skill --skill swiftui-pro或通过Claude Code应用市场安装:
/plugin marketplace add twostraws/SwiftUI-Agent-Skill
/plugin install swiftui-pro@swiftui-agent-skillUsage
使用方法
In Claude Code
在Claude Code中使用
/swiftui-proWith specific focus:
/swiftui-pro Check for deprecated API
/swiftui-pro Focus on accessibility
/swiftui-pro Review navigation patterns/swiftui-pro指定重点方向:
/swiftui-pro Check for deprecated API
/swiftui-pro Focus on accessibility
/swiftui-pro Review navigation patternsIn Codex
在Codex中使用
$swiftui-proWith specific instructions:
$swiftui-pro Look for performance issues
$swiftui-pro Check VoiceOver support$swiftui-pro指定具体指令:
$swiftui-pro Look for performance issues
$swiftui-pro Check VoiceOver supportNatural Language
自然语言指令
Use the SwiftUI Pro skill to review this view for best practices
Check this SwiftUI code with the agent skillUse the SwiftUI Pro skill to review this view for best practices
Check this SwiftUI code with the agent skillKey SwiftUI Best Practices
SwiftUI核心最佳实践
Modern Navigation
现代化导航
Use NavigationStack and NavigationPath (iOS 16+), not deprecated NavigationView:
swift
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List {
NavigationLink("Detail", value: "detail")
}
.navigationDestination(for: String.self) { value in
DetailView(item: value)
}
}
}
}Programmatic navigation:
swift
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
Button("Go to Detail") {
path.append("detail")
}
.navigationDestination(for: String.self) { value in
DetailView(item: value)
}
}
}
}使用NavigationStack和NavigationPath(iOS 16+),替代已废弃的NavigationView:
swift
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List {
NavigationLink("Detail", value: "detail")
}
.navigationDestination(for: String.self) { value in
DetailView(item: value)
}
}
}
}程序化导航:
swift
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
Button("Go to Detail") {
path.append("detail")
}
.navigationDestination(for: String.self) { value in
DetailView(item: value)
}
}
}
}State Management
状态管理
Use @State for view-local state:
swift
struct CounterView: View {
@State private var count = 0
var body: some View {
Button("Count: \(count)") {
count += 1
}
}
}Use @Observable for shared state (iOS 17+), not ObservableObject:
swift
@Observable
class AppState {
var username = ""
var isLoggedIn = false
}
struct ContentView: View {
let state = AppState()
var body: some View {
Text("User: \(state.username)")
.onChange(of: state.isLoggedIn) { oldValue, newValue in
print("Login state changed")
}
}
}Use @Bindable for bindings to Observable objects:
swift
struct ProfileView: View {
@Bindable var user: User
var body: some View {
TextField("Name", text: $user.name)
}
}使用@State管理视图本地状态:
swift
struct CounterView: View {
@State private var count = 0
var body: some View {
Button("Count: \(count)") {
count += 1
}
}
}使用@Observable管理共享状态(iOS 17+),替代ObservableObject:
swift
@Observable
class AppState {
var username = ""
var isLoggedIn = false
}
struct ContentView: View {
let state = AppState()
var body: some View {
Text("User: \(state.username)")
.onChange(of: state.isLoggedIn) { oldValue, newValue in
print("Login state changed")
}
}
}使用@Bindable绑定Observable对象:
swift
struct ProfileView: View {
@Bindable var user: User
var body: some View {
TextField("Name", text: $user.name)
}
}Accessibility
无障碍访问
Always provide accessibility labels for custom controls:
swift
struct CustomButton: View {
var body: some View {
Image(systemName: "star.fill")
.foregroundStyle(.yellow)
.onTapGesture {
// action
}
.accessibilityLabel("Favorite")
.accessibilityAddTraits(.isButton)
}
}Use accessibilityElement for grouped content:
swift
struct UserCard: View {
let name: String
let age: Int
var body: some View {
VStack {
Text(name)
Text("\(age) years old")
}
.accessibilityElement(children: .combine)
}
}Provide hints for complex interactions:
swift
Button("Delete") {
// delete action
}
.accessibilityHint("Deletes this item permanently")始终为自定义控件提供无障碍标签:
swift
struct CustomButton: View {
var body: some View {
Image(systemName: "star.fill")
.foregroundStyle(.yellow)
.onTapGesture {
// action
}
.accessibilityLabel("Favorite")
.accessibilityAddTraits(.isButton)
}
}使用accessibilityElement组合内容:
swift
struct UserCard: View {
let name: String
let age: Int
var body: some View {
VStack {
Text(name)
Text("\(age) years old")
}
.accessibilityElement(children: .combine)
}
}为复杂交互提供提示:
swift
Button("Delete") {
// delete action
}
.accessibilityHint("Deletes this item permanently")Layout
布局
Use modern layout containers:
swift
// Grid for structured layouts
Grid(alignment: .leading, horizontalSpacing: 20) {
GridRow {
Text("Name:")
Text("John")
}
GridRow {
Text("Age:")
Text("30")
}
}
// ViewThatFits for responsive layouts
ViewThatFits {
HStack {
content
}
VStack {
content
}
}Avoid nested geometry readers:
swift
// BAD
GeometryReader { outer in
GeometryReader { inner in
// layout code
}
}
// GOOD - use .containerRelativeFrame or layout protocol
Color.blue
.containerRelativeFrame(.horizontal) { width, axis in
width * 0.5
}使用现代化布局容器:
swift
// Grid用于结构化布局
Grid(alignment: .leading, horizontalSpacing: 20) {
GridRow {
Text("Name:")
Text("John")
}
GridRow {
Text("Age:")
Text("30")
}
}
// ViewThatFits用于响应式布局
ViewThatFits {
HStack {
content
}
VStack {
content
}
}避免嵌套GeometryReader:
swift
// 不良写法
GeometryReader { outer in
GeometryReader { inner in
// layout code
}
}
// 推荐写法 - 使用.containerRelativeFrame或布局协议
Color.blue
.containerRelativeFrame(.horizontal) { width, axis in
width * 0.5
}Performance
性能优化
Use lazy containers for large lists:
swift
// Always use LazyVStack/LazyHStack for scrolling content
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemView(item: item)
}
}
}Avoid expensive computations in body:
swift
struct ContentView: View {
@State private var items: [Item] = []
// BAD - computed every time body runs
var body: some View {
let sortedItems = items.sorted()
List(sortedItems) { item in
Text(item.name)
}
}
}
// GOOD - cache computed values
struct ContentView: View {
@State private var items: [Item] = []
private var sortedItems: [Item] {
items.sorted()
}
var body: some View {
List(sortedItems) { item in
Text(item.name)
}
}
}Use task modifiers instead of onAppear for async work:
swift
// GOOD
struct ContentView: View {
@State private var data: [Item] = []
var body: some View {
List(data) { item in
Text(item.name)
}
.task {
await loadData()
}
}
func loadData() async {
// async loading
}
}为大型列表使用懒加载容器:
swift
// 滚动内容始终使用LazyVStack/LazyHStack
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemView(item: item)
}
}
}避免在body中执行昂贵计算:
swift
struct ContentView: View {
@State private var items: [Item] = []
// 不良写法 - 每次body运行时都会重新计算
var body: some View {
let sortedItems = items.sorted()
List(sortedItems) { item in
Text(item.name)
}
}
}
// 推荐写法 - 缓存计算结果
struct ContentView: View {
@State private var items: [Item] = []
private var sortedItems: [Item] {
items.sorted()
}
var body: some View {
List(sortedItems) { item in
Text(item.name)
}
}
}使用task修饰符替代onAppear处理异步任务:
swift
// 推荐写法
struct ContentView: View {
@State private var data: [Item] = []
var body: some View {
List(data) { item in
Text(item.name)
}
.task {
await loadData()
}
}
func loadData() async {
// async loading
}
}Animations
动画
Use withAnimation for state-driven animations:
swift
struct AnimatedView: View {
@State private var isExpanded = false
var body: some View {
VStack {
if isExpanded {
Text("Details")
.transition(.move(edge: .top))
}
Button("Toggle") {
withAnimation(.spring(response: 0.3)) {
isExpanded.toggle()
}
}
}
}
}Use animation modifier for continuous animations:
swift
struct RotatingView: View {
@State private var rotation = 0.0
var body: some View {
Image(systemName: "arrow.clockwise")
.rotationEffect(.degrees(rotation))
.animation(.linear(duration: 1).repeatForever(autoreverses: false), value: rotation)
.onAppear {
rotation = 360
}
}
}使用withAnimation实现状态驱动动画:
swift
struct AnimatedView: View {
@State private var isExpanded = false
var body: some View {
VStack {
if isExpanded {
Text("Details")
.transition(.move(edge: .top))
}
Button("Toggle") {
withAnimation(.spring(response: 0.3)) {
isExpanded.toggle()
}
}
}
}
}使用animation修饰符实现连续动画:
swift
struct RotatingView: View {
@State private var rotation = 0.0
var body: some View {
Image(systemName: "arrow.clockwise")
.rotationEffect(.degrees(rotation))
.animation(.linear(duration: 1).repeatForever(autoreverses: false), value: rotation)
.onAppear {
rotation = 360
}
}
}Common Deprecated APIs to Avoid
需避免的常见废弃API
NavigationView → Use
NavigationStacksheet(isPresented:onDismiss:content:) with @State bool is fine, but for complex flows use
NavigationStackGeometryReader for sizing → Use or layout protocol
containerRelativeFrameObservableObject → Use macro (iOS 17+)
@Observable@Published → Not needed with
@Observable@StateObject → Use with objects
@State@ObservableonChange(of:perform:) → Use with old/new values
onChange(of:initial:_:)NavigationView → 使用
NavigationStack**sheet(isPresented:onDismiss:content:)**搭配@State布尔值是可行的,但复杂流程建议使用
NavigationStackGeometryReader用于尺寸设置 → 使用或布局协议
containerRelativeFrameObservableObject → 使用宏(iOS 17+)
@Observable@Published → 使用后不再需要
@Observable@StateObject → 使用搭配对象
@State@ObservableonChange(of:perform:) → 使用包含新旧值的
onChange(of:initial:_:)Configuration
配置
Project-wide Installation
全局安装
Install for all projects:
bash
npx skills add https://github.com/twostraws/swiftui-agent-skill --skill swiftui-pro为所有项目安装:
bash
npx skills add https://github.com/twostraws/swiftui-agent-skill --skill swiftui-proSelect "all projects" during installation
安装时选择"all projects"
undefinedundefinedProject-specific Installation
项目专属安装
Install for current project only:
bash
npx skills add https://github.com/twostraws/swiftui-agent-skill --skill swiftui-pro仅为当前项目安装:
bash
npx skills add https://github.com/twostraws/swiftui-agent-skill --skill swiftui-proSelect "this project only" during installation
安装时选择"this project only"
undefinedundefinedCommon Patterns
常见模式
Form with Validation
带验证的表单
swift
@Observable
class FormData {
var email = ""
var password = ""
var isValid: Bool {
!email.isEmpty && password.count >= 8
}
}
struct LoginForm: View {
@State private var formData = FormData()
var body: some View {
Form {
TextField("Email", text: $formData.email)
.textContentType(.emailAddress)
.keyboardType(.emailAddress)
SecureField("Password", text: $formData.password)
.textContentType(.password)
Button("Login") {
// login action
}
.disabled(!formData.isValid)
}
}
}swift
@Observable
class FormData {
var email = ""
var password = ""
var isValid: Bool {
!email.isEmpty && password.count >= 8
}
}
struct LoginForm: View {
@State private var formData = FormData()
var body: some View {
Form {
TextField("Email", text: $formData.email)
.textContentType(.emailAddress)
.keyboardType(.emailAddress)
SecureField("Password", text: $formData.password)
.textContentType(.password)
Button("Login") {
// login action
}
.disabled(!formData.isValid)
}
}
}Environment Injection
环境注入
swift
@Observable
class AppSettings {
var theme = "light"
var fontSize = 14.0
}
@main
struct MyApp: App {
@State private var settings = AppSettings()
var body: some Scene {
WindowGroup {
ContentView()
.environment(settings)
}
}
}
struct ContentView: View {
@Environment(AppSettings.self) private var settings
var body: some View {
Text("Theme: \(settings.theme)")
}
}swift
@Observable
class AppSettings {
var theme = "light"
var fontSize = 14.0
}
@main
struct MyApp: App {
@State private var settings = AppSettings()
var body: some Scene {
WindowGroup {
ContentView()
.environment(settings)
}
}
}
struct ContentView: View {
@Environment(AppSettings.self) private var settings
var body: some View {
Text("Theme: \(settings.theme)")
}
}Custom View Modifiers
自定义视图修饰符
swift
struct CardStyle: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(.background.secondary)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(radius: 2)
}
}
extension View {
func cardStyle() -> some View {
modifier(CardStyle())
}
}
// Usage
Text("Hello")
.cardStyle()swift
struct CardStyle: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(.background.secondary)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(radius: 2)
}
}
extension View {
func cardStyle() -> some View {
modifier(CardStyle())
}
}
// 使用示例
Text("Hello")
.cardStyle()Troubleshooting
故障排查
"Purple warnings" in console
控制台出现“紫色警告”
Usually caused by state updates during view updates. Use or :
TaskDispatchQueue.main.asyncswift
.onAppear {
Task {
await loadData()
}
}通常是由于视图更新期间修改状态导致的。使用或:
TaskDispatchQueue.main.asyncswift
.onAppear {
Task {
await loadData()
}
}Views not updating
视图未更新
Ensure Observable objects are properly injected:
swift
// BAD
struct ChildView: View {
let settings: AppSettings // Won't observe changes
}
// GOOD
struct ChildView: View {
@Environment(AppSettings.self) private var settings
}确保Observable对象已正确注入:
swift
// 不良写法
struct ChildView: View {
let settings: AppSettings // 无法监听变化
}
// 推荐写法
struct ChildView: View {
@Environment(AppSettings.self) private var settings
}Navigation state not persisting
导航状态未持久化
Use proper navigation path management:
swift
@State private var path = NavigationPath()
// Restore path from storage
.task {
if let savedPath = try? await loadPath() {
path = savedPath
}
}使用正确的导航路径管理方式:
swift
@State private var path = NavigationPath()
// 从存储中恢复路径
.task {
if let savedPath = try? await loadPath() {
path = savedPath
}
}Performance issues with List
List性能问题
Use lazy loading and id-based updates:
swift
List(items, id: \.id) { item in
ItemRow(item: item)
}
.id(items.map(\.id)) // Force refresh when needed使用懒加载和基于ID的更新:
swift
List(items, id: \.id) { item in
ItemRow(item: item)
}
.id(items.map(\.id)) // 需要时强制刷新Related Skills
相关技能
- SwiftData Pro - SwiftData best practices
- Swift Concurrency Pro - Async/await patterns
- Swift Testing Pro - Modern Swift testing
- SwiftData Pro - SwiftData最佳实践
- Swift Concurrency Pro - 异步/等待模式
- Swift Testing Pro - 现代化Swift测试
Resources
资源
- Hacking with Swift - Free SwiftUI tutorials
- Swift Agent Skills - Collection of Swift agent skills
- Agent Skills - Agent Skills format documentation
- Hacking with Swift - 免费SwiftUI教程
- Swift Agent Skills - Swift Agent技能合集
- Agent Skills - Agent Skills格式文档