用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/UitbreidenOS/UitKit --skill ios命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Guidelines and instructions for Agent execution state rollback rules
Guidelines and instructions for Agent execution step counters limits
Guidelines and instructions for Agent execution timeout limits setups
基于 SOC 职业分类
正在显示 SKILL.md
| name | ios |
| description | - Building iOS, iPadOS, or macOS apps with SwiftUI |
@State, @Binding, @ObservedObject, etc.)| Wrapper | Owner | Use case |
|---|---|---|
@State | The view itself | Local, value-type UI state (toggle, text field input, counter) |
@Binding | Parent passes it down | Two-way connection to a parent's @State |
@StateObject | The view itself | View creates and owns a reference-type ObservableObject |
@ObservedObject | Parent passes it in | View observes a reference-type object owned elsewhere |
@EnvironmentObject | Injected via .environmentObject() | Shared observable object propagated through the view hierarchy |
@Environment | SwiftUI environment | System values: colorScheme, dismiss, openURL, custom environment keys |
// @State — local value owned by this view
struct ToggleRow: View {
@State private var isOn = false
var body: some View {
Toggle("Notifications", isOn: $isOn) // $ produces a Binding
}
}
// @Binding — parent controls truth
struct ToggleRow: View {
@Binding var isOn: Bool // parent passes $parentState.isOn
var body: some View {
Toggle("Notifications", isOn: $isOn)
}
}
// @StateObject — view owns and creates the object
struct ProfileView: View {
@StateObject private var viewModel = ProfileViewModel()
var body: some View {
Text(viewModel.name)
}
}
// @ObservedObject — object is passed in, owned elsewhere
struct ProfileView: View {
@ObservedObject var viewModel: ProfileViewModel
body: {
(viewModel.name)
}
}
: {
session:
body: {
(session.displayName)
}
}
().environmentObject(())
Rule: use @StateObject when the view creates the object. Use @ObservedObject when the object is injected. Never create an ObservableObject directly in the body property — it will be recreated on every render.
// Models
struct User: Decodable {
let id: Int
let name: String
let email: String
}
// Network layer
struct APIClient {
let baseURL = URL(string: "https://api.example.com")!
let decoder = JSONDecoder()
func fetchUser(id: Int) async throws -> User {
let url = baseURL.appendingPathComponent("users/\(id)")
var request = URLRequest(url: url)
request.setValue("Bearer \(TokenStore.current)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
guard ().contains(http.statusCode) {
.httpError(http.statusCode)
}
decoder.decode(., from: data)
}
}
: {
user: ?
error: ?
isLoading
client ()
(: ) {
isLoading
{ isLoading }
{
user client.fetchUser(id: id)
} {
.error error
}
}
}
: {
vm ()
userId:
body: {
{
vm.isLoading {
()
} user vm.user {
(user.name)
} error vm.error {
()
}
}
.task { vm.load(id: userId) }
}
}
Always mark ViewModels @MainActor to ensure @Published mutations happen on the main thread.
// Persistence.swift
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let controller = PersistenceController(inMemory: true)
let ctx = controller.container.viewContext
// Insert sample data for previews
let item = Item(context: ctx)
item.timestamp = Date()
try? ctx.save()
return controller
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "MyApp") // matches .xcdatamodeld filename
if inMemory {
container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores { _, error in
if let error { fatalError("Core Data failed: ") }
}
container.viewContext.automaticallyMergesChangesFromParent
container.viewContext.mergePolicy
}
}
: {
persistence .shared
body: {
{
()
.environment(\.managedObjectContext, persistence.container.viewContext)
}
}
}
: {
(\.managedObjectContext) ctx
(
sortDescriptors: [(\.timestamp, order: .reverse)],
animation: .default
)
items: <>
() {
item (context: ctx)
item.timestamp ()
ctx.save()
}
}
For background operations, use container.newBackgroundContext() or container.performBackgroundTask { ctx in }.
import Combine
class SearchViewModel: ObservableObject {
@Published var query = ""
@Published var results: [Product] = []
@Published var isLoading = false
private var cancellables = Set<AnyCancellable>()
private let api = APIClient()
init() {
$query
.debounce(for: .milliseconds(300), scheduler: RunLoop.main)
.removeDuplicates()
.filter { $0.count >= 2 }
.handleEvents(receiveOutput: { [weak self] _ in self?.isLoading = true })
.flatMap { [weak self] q -> AnyPublisher<[Product], Never> in
guard let self else { return Empty().eraseToAnyPublisher() }
.api.search(query: q)
.catch { ([]) }
.eraseToAnyPublisher()
}
.receive(on: .main)
.handleEvents(receiveOutput: { [ ] .isLoading })
.assign(to: )
}
}
Use .sink when you need side effects. Use .assign(to:) to drive a @Published property. Always store subscriptions in Set<AnyCancellable> or use the &$published form to avoid premature cancellation.
Before submitting to App Store Connect:
Technical requirements:
Info.plist (camera, location, microphone, contacts, etc.)LaunchScreen.storyboard configuredPrivacy:
PrivacyInfo.xcprivacy) required for apps using specific APIs (file timestamp APIs, system boot time, disk space, active keyboard, user defaults)App Store Connect:
Testing before submission:
A settings screen demonstrating multiple property wrappers together:
@MainActor
class SettingsViewModel: ObservableObject {
@Published var notificationsEnabled = false
@Published var theme: AppTheme = .system
func save() async {
try? await UserPreferencesAPI.save(
notifications: notificationsEnabled,
theme: theme
)
}
}
struct SettingsView: View {
@StateObject private var vm = SettingsViewModel()
@EnvironmentObject var session: UserSession
@Environment(\.dismiss) private var dismiss
var body: some View {
Form {
Section("Account") {
Text(session.email).foregroundStyle(.secondary)
}
Section("Preferences") {
Toggle("Notifications", isOn: $vm.notificationsEnabled)
Picker("Theme", selection: $vm.theme) {
ForEach(AppTheme.allCases) { (.label).tag() }
}
}
}
.navigationTitle()
.toolbar {
(placement: .confirmationAction) {
() {
{
vm.save()
dismiss()
}
}
}
}
}
}