mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
Merge pull request #244 from maxdimitrov/feat/rules/swift
LGTM — Swift rules and SwiftUI patterns skill. Pure documentation, no security concerns.
This commit is contained in:
@@ -16,7 +16,8 @@ rules/
|
|||||||
│ └── security.md
|
│ └── security.md
|
||||||
├── typescript/ # TypeScript/JavaScript specific
|
├── typescript/ # TypeScript/JavaScript specific
|
||||||
├── python/ # Python specific
|
├── python/ # Python specific
|
||||||
└── golang/ # Go specific
|
├── golang/ # Go specific
|
||||||
|
└── swift/ # Swift specific
|
||||||
```
|
```
|
||||||
|
|
||||||
- **common/** contains universal principles — no language-specific code examples.
|
- **common/** contains universal principles — no language-specific code examples.
|
||||||
@@ -31,6 +32,7 @@ rules/
|
|||||||
./install.sh typescript
|
./install.sh typescript
|
||||||
./install.sh python
|
./install.sh python
|
||||||
./install.sh golang
|
./install.sh golang
|
||||||
|
./install.sh swift
|
||||||
|
|
||||||
# Install multiple languages at once
|
# Install multiple languages at once
|
||||||
./install.sh typescript python
|
./install.sh typescript python
|
||||||
@@ -52,6 +54,7 @@ cp -r rules/common ~/.claude/rules/common
|
|||||||
cp -r rules/typescript ~/.claude/rules/typescript
|
cp -r rules/typescript ~/.claude/rules/typescript
|
||||||
cp -r rules/python ~/.claude/rules/python
|
cp -r rules/python ~/.claude/rules/python
|
||||||
cp -r rules/golang ~/.claude/rules/golang
|
cp -r rules/golang ~/.claude/rules/golang
|
||||||
|
cp -r rules/swift ~/.claude/rules/swift
|
||||||
|
|
||||||
# Attention ! ! ! Configure according to your actual project requirements; the configuration here is for reference only.
|
# Attention ! ! ! Configure according to your actual project requirements; the configuration here is for reference only.
|
||||||
```
|
```
|
||||||
|
|||||||
47
rules/swift/coding-style.md
Normal file
47
rules/swift/coding-style.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/*.swift"
|
||||||
|
- "**/Package.swift"
|
||||||
|
---
|
||||||
|
# Swift Coding Style
|
||||||
|
|
||||||
|
> This file extends [common/coding-style.md](../common/coding-style.md) with Swift specific content.
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
|
||||||
|
- **SwiftFormat** for auto-formatting, **SwiftLint** for style enforcement
|
||||||
|
- `swift-format` is bundled with Xcode 16+ as an alternative
|
||||||
|
|
||||||
|
## Immutability
|
||||||
|
|
||||||
|
- Prefer `let` over `var` — define everything as `let` and only change to `var` if the compiler requires it
|
||||||
|
- Use `struct` with value semantics by default; use `class` only when identity or reference semantics are needed
|
||||||
|
|
||||||
|
## Naming
|
||||||
|
|
||||||
|
Follow [Apple API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/):
|
||||||
|
|
||||||
|
- Clarity at the point of use — omit needless words
|
||||||
|
- Name methods and properties for their roles, not their types
|
||||||
|
- Use `static let` for constants over global constants
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Use typed throws (Swift 6+) and pattern matching:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
func load(id: String) throws(LoadError) -> Item {
|
||||||
|
guard let data = try? read(from: path) else {
|
||||||
|
throw .fileNotFound(id)
|
||||||
|
}
|
||||||
|
return try decode(data)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Concurrency
|
||||||
|
|
||||||
|
Enable Swift 6 strict concurrency checking. Prefer:
|
||||||
|
|
||||||
|
- `Sendable` value types for data crossing isolation boundaries
|
||||||
|
- Actors for shared mutable state
|
||||||
|
- Structured concurrency (`async let`, `TaskGroup`) over unstructured `Task {}`
|
||||||
20
rules/swift/hooks.md
Normal file
20
rules/swift/hooks.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/*.swift"
|
||||||
|
- "**/Package.swift"
|
||||||
|
---
|
||||||
|
# Swift Hooks
|
||||||
|
|
||||||
|
> This file extends [common/hooks.md](../common/hooks.md) with Swift specific content.
|
||||||
|
|
||||||
|
## PostToolUse Hooks
|
||||||
|
|
||||||
|
Configure in `~/.claude/settings.json`:
|
||||||
|
|
||||||
|
- **SwiftFormat**: Auto-format `.swift` files after edit
|
||||||
|
- **SwiftLint**: Run lint checks after editing `.swift` files
|
||||||
|
- **swift build**: Type-check modified packages after edit
|
||||||
|
|
||||||
|
## Warning
|
||||||
|
|
||||||
|
Flag `print()` statements — use `os.Logger` or structured logging instead for production code.
|
||||||
66
rules/swift/patterns.md
Normal file
66
rules/swift/patterns.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/*.swift"
|
||||||
|
- "**/Package.swift"
|
||||||
|
---
|
||||||
|
# Swift Patterns
|
||||||
|
|
||||||
|
> This file extends [common/patterns.md](../common/patterns.md) with Swift specific content.
|
||||||
|
|
||||||
|
## Protocol-Oriented Design
|
||||||
|
|
||||||
|
Define small, focused protocols. Use protocol extensions for shared defaults:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
protocol Repository: Sendable {
|
||||||
|
associatedtype Item: Identifiable & Sendable
|
||||||
|
func find(by id: Item.ID) async throws -> Item?
|
||||||
|
func save(_ item: Item) async throws
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Value Types
|
||||||
|
|
||||||
|
- Use structs for data transfer objects and models
|
||||||
|
- Use enums with associated values to model distinct states:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
enum LoadState<T: Sendable>: Sendable {
|
||||||
|
case idle
|
||||||
|
case loading
|
||||||
|
case loaded(T)
|
||||||
|
case failed(Error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Actor Pattern
|
||||||
|
|
||||||
|
Use actors for shared mutable state instead of locks or dispatch queues:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
actor Cache<Key: Hashable & Sendable, Value: Sendable> {
|
||||||
|
private var storage: [Key: Value] = [:]
|
||||||
|
|
||||||
|
func get(_ key: Key) -> Value? { storage[key] }
|
||||||
|
func set(_ key: Key, value: Value) { storage[key] = value }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependency Injection
|
||||||
|
|
||||||
|
Inject protocols with default parameters — production uses defaults, tests inject mocks:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
struct UserService {
|
||||||
|
private let repository: any UserRepository
|
||||||
|
|
||||||
|
init(repository: any UserRepository = DefaultUserRepository()) {
|
||||||
|
self.repository = repository
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
See skill: `swift-actor-persistence` for actor-based persistence patterns.
|
||||||
|
See skill: `swift-protocol-di-testing` for protocol-based DI and testing.
|
||||||
33
rules/swift/security.md
Normal file
33
rules/swift/security.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/*.swift"
|
||||||
|
- "**/Package.swift"
|
||||||
|
---
|
||||||
|
# Swift Security
|
||||||
|
|
||||||
|
> This file extends [common/security.md](../common/security.md) with Swift specific content.
|
||||||
|
|
||||||
|
## Secret Management
|
||||||
|
|
||||||
|
- Use **Keychain Services** for sensitive data (tokens, passwords, keys) — never `UserDefaults`
|
||||||
|
- Use environment variables or `.xcconfig` files for build-time secrets
|
||||||
|
- Never hardcode secrets in source — decompilation tools extract them trivially
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let apiKey = ProcessInfo.processInfo.environment["API_KEY"]
|
||||||
|
guard let apiKey, !apiKey.isEmpty else {
|
||||||
|
fatalError("API_KEY not configured")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transport Security
|
||||||
|
|
||||||
|
- App Transport Security (ATS) is enforced by default — do not disable it
|
||||||
|
- Use certificate pinning for critical endpoints
|
||||||
|
- Validate all server certificates
|
||||||
|
|
||||||
|
## Input Validation
|
||||||
|
|
||||||
|
- Sanitize all user input before display to prevent injection
|
||||||
|
- Use `URL(string:)` with validation rather than force-unwrapping
|
||||||
|
- Validate data from external sources (APIs, deep links, pasteboard) before processing
|
||||||
45
rules/swift/testing.md
Normal file
45
rules/swift/testing.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
paths:
|
||||||
|
- "**/*.swift"
|
||||||
|
- "**/Package.swift"
|
||||||
|
---
|
||||||
|
# Swift Testing
|
||||||
|
|
||||||
|
> This file extends [common/testing.md](../common/testing.md) with Swift specific content.
|
||||||
|
|
||||||
|
## Framework
|
||||||
|
|
||||||
|
Use **Swift Testing** (`import Testing`) for new tests. Use `@Test` and `#expect`:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@Test("User creation validates email")
|
||||||
|
func userCreationValidatesEmail() throws {
|
||||||
|
#expect(throws: ValidationError.invalidEmail) {
|
||||||
|
try User(email: "not-an-email")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Isolation
|
||||||
|
|
||||||
|
Each test gets a fresh instance — set up in `init`, tear down in `deinit`. No shared mutable state between tests.
|
||||||
|
|
||||||
|
## Parameterized Tests
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@Test("Validates formats", arguments: ["json", "xml", "csv"])
|
||||||
|
func validatesFormat(format: String) throws {
|
||||||
|
let parser = try Parser(format: format)
|
||||||
|
#expect(parser.isValid)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Coverage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
swift test --enable-code-coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
See skill: `swift-protocol-di-testing` for protocol-based dependency injection and mock patterns with Swift Testing.
|
||||||
259
skills/swiftui-patterns/SKILL.md
Normal file
259
skills/swiftui-patterns/SKILL.md
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
---
|
||||||
|
name: swiftui-patterns
|
||||||
|
description: SwiftUI architecture patterns, state management with @Observable, view composition, navigation, performance optimization, and modern iOS/macOS UI best practices.
|
||||||
|
---
|
||||||
|
|
||||||
|
# SwiftUI Patterns
|
||||||
|
|
||||||
|
Modern SwiftUI patterns for building declarative, performant user interfaces on Apple platforms. Covers the Observation framework, view composition, type-safe navigation, and performance optimization.
|
||||||
|
|
||||||
|
## When to Activate
|
||||||
|
|
||||||
|
- Building SwiftUI views and managing state (`@State`, `@Observable`, `@Binding`)
|
||||||
|
- Designing navigation flows with `NavigationStack`
|
||||||
|
- Structuring view models and data flow
|
||||||
|
- Optimizing rendering performance for lists and complex layouts
|
||||||
|
- Working with environment values and dependency injection in SwiftUI
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
### Property Wrapper Selection
|
||||||
|
|
||||||
|
Choose the simplest wrapper that fits:
|
||||||
|
|
||||||
|
| Wrapper | Use Case |
|
||||||
|
|---------|----------|
|
||||||
|
| `@State` | View-local value types (toggles, form fields, sheet presentation) |
|
||||||
|
| `@Binding` | Two-way reference to parent's `@State` |
|
||||||
|
| `@Observable` class + `@State` | Owned model with multiple properties |
|
||||||
|
| `@Observable` class (no wrapper) | Read-only reference passed from parent |
|
||||||
|
| `@Bindable` | Two-way binding to an `@Observable` property |
|
||||||
|
| `@Environment` | Shared dependencies injected via `.environment()` |
|
||||||
|
|
||||||
|
### @Observable ViewModel
|
||||||
|
|
||||||
|
Use `@Observable` (not `ObservableObject`) — it tracks property-level changes so SwiftUI only re-renders views that read the changed property:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@Observable
|
||||||
|
final class ItemListViewModel {
|
||||||
|
private(set) var items: [Item] = []
|
||||||
|
private(set) var isLoading = false
|
||||||
|
var searchText = ""
|
||||||
|
|
||||||
|
private let repository: any ItemRepository
|
||||||
|
|
||||||
|
init(repository: any ItemRepository = DefaultItemRepository()) {
|
||||||
|
self.repository = repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func load() async {
|
||||||
|
isLoading = true
|
||||||
|
defer { isLoading = false }
|
||||||
|
items = (try? await repository.fetchAll()) ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### View Consuming the ViewModel
|
||||||
|
|
||||||
|
```swift
|
||||||
|
struct ItemListView: View {
|
||||||
|
@State private var viewModel: ItemListViewModel
|
||||||
|
|
||||||
|
init(viewModel: ItemListViewModel = ItemListViewModel()) {
|
||||||
|
_viewModel = State(initialValue: viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
List(viewModel.items) { item in
|
||||||
|
ItemRow(item: item)
|
||||||
|
}
|
||||||
|
.searchable(text: $viewModel.searchText)
|
||||||
|
.overlay { if viewModel.isLoading { ProgressView() } }
|
||||||
|
.task { await viewModel.load() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Injection
|
||||||
|
|
||||||
|
Replace `@EnvironmentObject` with `@Environment`:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Inject
|
||||||
|
ContentView()
|
||||||
|
.environment(authManager)
|
||||||
|
|
||||||
|
// Consume
|
||||||
|
struct ProfileView: View {
|
||||||
|
@Environment(AuthManager.self) private var auth
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Text(auth.currentUser?.name ?? "Guest")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## View Composition
|
||||||
|
|
||||||
|
### Extract Subviews to Limit Invalidation
|
||||||
|
|
||||||
|
Break views into small, focused structs. When state changes, only the subview reading that state re-renders:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
struct OrderView: View {
|
||||||
|
@State private var viewModel = OrderViewModel()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
OrderHeader(title: viewModel.title)
|
||||||
|
OrderItemList(items: viewModel.items)
|
||||||
|
OrderTotal(total: viewModel.total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ViewModifier for Reusable Styling
|
||||||
|
|
||||||
|
```swift
|
||||||
|
struct CardModifier: ViewModifier {
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.padding()
|
||||||
|
.background(.regularMaterial)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func cardStyle() -> some View {
|
||||||
|
modifier(CardModifier())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Navigation
|
||||||
|
|
||||||
|
### Type-Safe NavigationStack
|
||||||
|
|
||||||
|
Use `NavigationStack` with `NavigationPath` for programmatic, type-safe routing:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@Observable
|
||||||
|
final class Router {
|
||||||
|
var path = NavigationPath()
|
||||||
|
|
||||||
|
func navigate(to destination: Destination) {
|
||||||
|
path.append(destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
func popToRoot() {
|
||||||
|
path = NavigationPath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Destination: Hashable {
|
||||||
|
case detail(Item.ID)
|
||||||
|
case settings
|
||||||
|
case profile(User.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RootView: View {
|
||||||
|
@State private var router = Router()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack(path: $router.path) {
|
||||||
|
HomeView()
|
||||||
|
.navigationDestination(for: Destination.self) { dest in
|
||||||
|
switch dest {
|
||||||
|
case .detail(let id): ItemDetailView(itemID: id)
|
||||||
|
case .settings: SettingsView()
|
||||||
|
case .profile(let id): ProfileView(userID: id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.environment(router)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
### Use Lazy Containers for Large Collections
|
||||||
|
|
||||||
|
`LazyVStack` and `LazyHStack` create views only when visible:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
ScrollView {
|
||||||
|
LazyVStack(spacing: 8) {
|
||||||
|
ForEach(items) { item in
|
||||||
|
ItemRow(item: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stable Identifiers
|
||||||
|
|
||||||
|
Always use stable, unique IDs in `ForEach` — avoid using array indices:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Use Identifiable conformance or explicit id
|
||||||
|
ForEach(items, id: \.stableID) { item in
|
||||||
|
ItemRow(item: item)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid Expensive Work in body
|
||||||
|
|
||||||
|
- Never perform I/O, network calls, or heavy computation inside `body`
|
||||||
|
- Use `.task {}` for async work — it cancels automatically when the view disappears
|
||||||
|
- Use `.sensoryFeedback()` and `.geometryGroup()` sparingly in scroll views
|
||||||
|
- Minimize `.shadow()`, `.blur()`, and `.mask()` in lists — they trigger offscreen rendering
|
||||||
|
|
||||||
|
### Equatable Conformance
|
||||||
|
|
||||||
|
For views with expensive bodies, conform to `Equatable` to skip unnecessary re-renders:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
struct ExpensiveChartView: View, Equatable {
|
||||||
|
let dataPoints: [DataPoint] // DataPoint must conform to Equatable
|
||||||
|
|
||||||
|
static func == (lhs: Self, rhs: Self) -> Bool {
|
||||||
|
lhs.dataPoints == rhs.dataPoints
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
// Complex chart rendering
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Previews
|
||||||
|
|
||||||
|
Use `#Preview` macro with inline mock data for fast iteration:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
#Preview("Empty state") {
|
||||||
|
ItemListView(viewModel: ItemListViewModel(repository: EmptyMockRepository()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Loaded") {
|
||||||
|
ItemListView(viewModel: ItemListViewModel(repository: PopulatedMockRepository()))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
- Using `ObservableObject` / `@Published` / `@StateObject` / `@EnvironmentObject` in new code — migrate to `@Observable`
|
||||||
|
- Putting async work directly in `body` or `init` — use `.task {}` or explicit load methods
|
||||||
|
- Creating view models as `@State` inside child views that don't own the data — pass from parent instead
|
||||||
|
- Using `AnyView` type erasure — prefer `@ViewBuilder` or `Group` for conditional views
|
||||||
|
- Ignoring `Sendable` requirements when passing data to/from actors
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
See skill: `swift-actor-persistence` for actor-based persistence patterns.
|
||||||
|
See skill: `swift-protocol-di-testing` for protocol-based DI and testing with Swift Testing.
|
||||||
Reference in New Issue
Block a user