solid-swift
SOLID principles for Swift 6 and SwiftUI (iOS 26+). Apple recommended patterns, @Observable, actors, Preview-driven development.
When & Why to Use This Skill
This Claude skill acts as an expert architect and senior developer for modern iOS development, specifically optimized for Swift 6 and SwiftUI (iOS 26+). It enforces strict SOLID principles, Apple-recommended design patterns, and modern concurrency models like actors and @Observable. By integrating real-time documentation research and build validation, it ensures the delivery of high-quality, maintainable, and future-proof Apple platform codebases.
Use Cases
- Architecting scalable SwiftUI applications using a modular structure with clear separation between Protocols, Services, ViewModels, and Views.
- Refactoring legacy Swift codebases to adopt Swift 6 concurrency standards, including actors, @MainActor, and Sendable types.
- Implementing 'Preview-Driven Development' to ensure every UI component is fully testable and visually verified within Xcode Previews.
- Enforcing strict clean code standards such as limiting file lengths, mandatory Swift documentation, and localized string catalogs for global readiness.
- Automating the research and validation process using Apple Docs MCP and XcodeBuildMCP to ensure code aligns with the latest 2025-2026 API standards.
| name | solid-swift |
|---|---|
| description | SOLID principles for Swift 6 and SwiftUI (iOS 26+). Apple recommended patterns, @Observable, actors, Preview-driven development. |
SOLID Swift - Apple Best Practices 2025
Current Date (CRITICAL)
Today: January 2026 - ALWAYS use the current year for your searches. Search with "2025" or "2026", NEVER with past years.
MANDATORY: Research Before Coding
CRITICAL: Check today's date first, then search documentation and web BEFORE writing any code.
Priority Order (2026)
⭐ Apple Docs MCP (PRIMARY) - Official Apple documentation with WWDC 2014-2025
- Search SwiftUI, UIKit, Foundation, CoreData, ARKit docs
- Get framework details and symbol information
- Search WWDC sessions with transcripts (offline access)
- Access Apple sample code
- See:
mcp-tools/apple-docs-mcp.md
Context7 (SECONDARY) - For third-party libraries and community packages
- Use only if Apple Docs MCP doesn't have the answer
- Good for SPM packages, community libraries
Exa web search (TERTIARY) - Latest trends and blog posts
- Use with current year for newest patterns
- Community tutorials and articles
Build Validation (NEW 2026)
- ⭐ XcodeBuildMCP (MANDATORY after code changes)
- Build project to validate changes
- Inspect build errors autonomously
- Clean builds when needed
- See:
mcp-tools/xcode-build-mcp.md
WORKFLOW (2026):
1. Check date
2. Apple Docs MCP: Search API/WWDC (current year)
3. If not found → Context7 → Exa web search
4. Apply latest patterns
5. Code implementation
6. XcodeBuildMCP: Build to validate
7. Fix errors if any → Rebuild
Search queries (replace YYYY with current year):
Apple Docs MCP:
SwiftUI [component] YYYY[Framework] new APIs- WWDC sessions:
[topic] WWDC YYYY
Exa web search (if Apple Docs insufficient):
Swift [feature] YYYY best practicesSwiftUI [component] YYYY tutorial
Never assume - always verify current APIs and patterns exist for the current year.
Codebase Analysis (MANDATORY)
Before ANY implementation:
- Explore project structure to understand architecture
- Read existing related files to follow established patterns
- Identify naming conventions, coding style, and patterns used
- Understand data flow and dependencies
Continue implementation by:
- Following existing patterns and conventions
- Matching the coding style already in place
- Respecting the established architecture
- Integrating with existing services/components
DRY - Reuse Before Creating (MANDATORY)
Before writing ANY new code:
- Search existing codebase for similar functionality
- Check shared locations:
Core/Extensions/,Core/Utilities/,Core/Protocols/ - If similar code exists → extend/reuse instead of duplicate
When creating new code:
- Extract repeated logic (3+ occurrences) into shared helpers
- Place shared utilities in
Core/Utilities/ - Use Extensions for type enhancements
- Document reusable functions with
///
Absolute Rules (MANDATORY)
1. Files < 150 lines
- Split at 120 lines - Never exceed 150
- Views < 80 lines (extract subviews at 30+)
- ViewModels < 100 lines
- Services < 100 lines
- Models < 50 lines
2. Protocols Separated
Sources/
├── Protocols/ # Protocols ONLY
│ ├── UserServiceProtocol.swift
│ └── AuthProviderProtocol.swift
├── Services/ # Implementations
│ └── UserService.swift
├── ViewModels/ # @Observable classes
│ └── UserViewModel.swift
└── Views/ # SwiftUI Views
└── UserView.swift
3. Swift Documentation Mandatory
/// Fetches user by ID from remote API.
///
/// - Parameter id: User unique identifier
/// - Returns: User if found, nil otherwise
/// - Throws: `NetworkError` on connection failure
func fetchUser(id: String) async throws -> User?
4. Preview-Driven Development (Apple)
Every View MUST have a #Preview:
#Preview {
UserProfileView(user: .preview)
}
#Preview("Loading State") {
UserProfileView(user: nil)
}
Apple Architecture 2025
Recommended: @Observable + Services
Sources/
├── App/
│ └── MyApp.swift
├── Features/ # Feature modules
│ ├── Auth/
│ │ ├── Views/
│ │ ├── ViewModels/
│ │ ├── Services/
│ │ └── Protocols/
│ └── Profile/
├── Core/ # Shared
│ ├── Services/
│ ├── Models/
│ ├── Protocols/
│ └── Extensions/
└── Resources/
@Observable over ObservableObject
// ❌ OLD - ObservableObject
class UserViewModel: ObservableObject {
@Published var user: User?
}
// ✅ NEW - @Observable (iOS 17+)
@Observable
final class UserViewModel {
var user: User?
var isLoading = false
private let service: UserServiceProtocol
init(service: UserServiceProtocol) {
self.service = service
}
}
SOLID Principles for Swift
S - Single Responsibility
1 type = 1 purpose
// ❌ BAD - View does everything
struct UserView: View {
@State private var user: User?
var body: some View {
// fetching, validation, formatting, rendering...
}
}
// ✅ GOOD - Separation
struct UserView: View {
@State private var viewModel: UserViewModel
var body: some View {
UserContent(user: viewModel.user)
}
}
O - Open/Closed
Protocols for extensibility
// Protocols/AuthProviderProtocol.swift
protocol AuthProviderProtocol: Sendable {
func signIn(credentials: Credentials) async throws -> Session
func signOut() async
}
// Services/AppleAuthProvider.swift
final class AppleAuthProvider: AuthProviderProtocol { }
// Services/GoogleAuthProvider.swift
final class GoogleAuthProvider: AuthProviderProtocol { }
L - Liskov Substitution
All implementations respect contracts
// Any provider works
let auth: AuthProviderProtocol = AppleAuthProvider()
// or
let auth: AuthProviderProtocol = GoogleAuthProvider()
// Both work identically
try await auth.signIn(credentials: creds)
I - Interface Segregation
Small, focused protocols
// ❌ BAD - Too broad
protocol UserProtocol {
func fetch() async
func update() async
func delete() async
func sendNotification() async
}
// ✅ GOOD - Separated
protocol Fetchable { func fetch() async }
protocol Updatable { func update() async }
protocol Deletable { func delete() async }
D - Dependency Inversion
Depend on protocols, inject implementations
// ViewModel depends on protocol
@Observable
final class UserViewModel {
private let service: UserServiceProtocol
init(service: UserServiceProtocol) {
self.service = service
}
}
// Injection at app level
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environment(UserViewModel(service: UserService()))
}
}
}
Swift 6 Concurrency
Actor for Shared State
actor UserCache {
private var users: [String: User] = [:]
func get(_ id: String) -> User? {
users[id]
}
func set(_ user: User) {
users[user.id] = user
}
}
@MainActor for UI
@MainActor
@Observable
final class UserViewModel {
var user: User?
var isLoading = false
func load() async {
isLoading = true
defer { isLoading = false }
user = try? await service.fetchUser()
}
}
Sendable Types
// Models must be Sendable for concurrency
struct User: Codable, Sendable {
let id: String
let name: String
let email: String
}
Structured Concurrency
// ✅ GOOD - Task groups
func loadDashboard() async throws -> Dashboard {
async let user = fetchUser()
async let stats = fetchStats()
async let notifications = fetchNotifications()
return Dashboard(
user: try await user,
stats: try await stats,
notifications: try await notifications
)
}
SwiftUI Templates
View (< 80 lines)
import SwiftUI
/// User profile view displaying user information.
struct UserProfileView: View {
let user: User?
var body: some View {
Group {
if let user {
UserContent(user: user)
} else {
ProgressView()
}
}
.navigationTitle("profile.title")
}
}
// MARK: - Subviews
private struct UserContent: View {
let user: User
var body: some View {
VStack {
Text(user.name)
Text(user.email)
}
}
}
// MARK: - Preview
#Preview {
UserProfileView(user: .preview)
}
ViewModel (< 100 lines)
import Foundation
/// ViewModel for user profile screen.
@MainActor
@Observable
final class UserProfileViewModel {
// MARK: - State
var user: User?
var isLoading = false
var error: Error?
// MARK: - Dependencies
private let service: UserServiceProtocol
// MARK: - Init
init(service: UserServiceProtocol) {
self.service = service
}
// MARK: - Actions
/// Loads user profile from API.
func load() async {
isLoading = true
defer { isLoading = false }
do {
user = try await service.fetchCurrentUser()
} catch {
self.error = error
}
}
}
Protocol
import Foundation
/// Protocol for user-related operations.
protocol UserServiceProtocol: Sendable {
/// Fetches the current authenticated user.
func fetchCurrentUser() async throws -> User
/// Updates user profile.
func updateUser(_ user: User) async throws -> User
}
Service
import Foundation
/// Implementation of UserServiceProtocol using URLSession.
final class UserService: UserServiceProtocol {
private let session: URLSession
init(session: URLSession = .shared) {
self.session = session
}
func fetchCurrentUser() async throws -> User {
let url = URL(string: "https://api.example.com/me")!
let (data, _) = try await session.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
func updateUser(_ user: User) async throws -> User {
// Implementation...
return user
}
}
Localization (MANDATORY)
All user-facing text MUST use String Catalogs:
// ✅ GOOD - Localized
Text("profile.welcome.title")
Button("button.save") { }
// With interpolation
Text("profile.greeting \(user.name)")
// ❌ BAD - Hardcoded
Text("Welcome!")
Button("Save") { }
Key naming: module.screen.element
iOS 26 / WWDC 2025
Liquid Glass Design
.glassBackgroundEffect()
.liquidGlass()
SwiftUI Performance
- 6x faster list loading
- 16x faster updates on macOS
- Use SwiftUI Performance Instrument
3D Layouts (visionOS)
SpatialLayout {
Model3D(named: "object")
}
Response Guidelines
- Research first - MANDATORY: Search Context7 + Exa before ANY code
- Show complete code - Working examples, not snippets
- Explain decisions - Why this pattern over alternatives
- Include previews - Always add #Preview for views
- Handle errors - Never ignore, always handle gracefully
- Consider accessibility - VoiceOver, Dynamic Type
- Document code - /// for public APIs
Forbidden
- ❌ Coding without researching docs first (ALWAYS research)
- ❌ Using outdated APIs without checking current year docs
- ❌ Files > 150 lines
- ❌ Protocols in implementation files
- ❌ ObservableObject (use @Observable)
- ❌ Completion handlers (use async/await)
- ❌ Missing #Preview
- ❌ Hardcoded strings (use String Catalogs)
- ❌ Force unwrap without validation
- ❌ Missing /// documentation
- ❌ Views > 80 lines without extraction
- ❌ Non-Sendable types in async contexts