swift-ios-development

Swift iOS Development Standards

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "swift-ios-development" with this command: npx skills add miicolas/apply/miicolas-apply-swift-ios-development

Swift iOS Development Standards

Professional guidelines for building production-ready iOS applications with Swift and SwiftUI.

Core Principles

  • Swift Best Practices: Modern Swift syntax, protocols, and type safety

  • MVVM Architecture: Separation of UI (View), logic (ViewModel), and data (Model)

  • SwiftUI Declarative: Leverage SwiftUI's declarative paradigm

  • Async/Await: Use modern concurrency for network and async operations

  • Error Handling: Comprehensive error handling with proper user feedback

Standard Project Structure

YourApp/ ├── App/ │ └── YourAppApp.swift # App entry point ├── Models/ # Data models ├── ViewModels/ # Business logic and state ├── Views/ # SwiftUI views ├── Services/ # Networking, persistence, etc. ├── Config/ # Configuration files └── Utils/ # Helper functions and extensions

MVVM Architecture Pattern

Model (Data Layer)

// Models/User.swift import Foundation

struct User: Codable, Identifiable { let id: String let name: String let email: String let createdAt: Date }

// Response wrapper for API struct APIResponse<T: Codable>: Codable { let data: T }

struct ErrorResponse: Codable { let error: ErrorDetail }

struct ErrorDetail: Codable { let message: String let code: String? }

ViewModel (Business Logic)

// ViewModels/UsersViewModel.swift import Foundation

@MainActor class UsersViewModel: ObservableObject { @Published var users: [User] = [] @Published var isLoading = false @Published var errorMessage: String?

private let apiService: APIService

init(apiService: APIService = APIService.shared) {
    self.apiService = apiService
}

func fetchUsers() async {
    isLoading = true
    errorMessage = nil

    do {
        users = try await apiService.fetchUsers()
    } catch {
        errorMessage = error.localizedDescription
    }

    isLoading = false
}

func deleteUser(_ user: User) async {
    do {
        try await apiService.deleteUser(id: user.id)
        users.removeAll { $0.id == user.id }
    } catch {
        errorMessage = "Failed to delete user: \(error.localizedDescription)"
    }
}

}

View (UI Layer)

// Views/UsersView.swift import SwiftUI

struct UsersView: View { @StateObject private var viewModel = UsersViewModel()

var body: some View {
    NavigationStack {
        Group {
            if viewModel.isLoading {
                ProgressView("Loading users...")
            } else if let error = viewModel.errorMessage {
                ErrorView(message: error) {
                    Task { await viewModel.fetchUsers() }
                }
            } else {
                usersList
            }
        }
        .navigationTitle("Users")
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button("Add", systemImage: "plus") {
                    // Show add user sheet
                }
            }
        }
        .task {
            await viewModel.fetchUsers()
        }
    }
}

private var usersList: some View {
    List {
        ForEach(viewModel.users) { user in
            NavigationLink(value: user) {
                UserRow(user: user)
            }
        }
        .onDelete { indexSet in
            for index in indexSet {
                Task {
                    await viewModel.deleteUser(viewModel.users[index])
                }
            }
        }
    }
    .navigationDestination(for: User.self) { user in
        UserDetailView(user: user)
    }
}

}

// Reusable error view struct ErrorView: View { let message: String let retry: () -> Void

var body: some View {
    VStack(spacing: 16) {
        Image(systemName: "exclamationmark.triangle")
            .font(.largeTitle)
            .foregroundStyle(.red)

        Text(message)
            .multilineTextAlignment(.center)
            .foregroundStyle(.secondary)

        Button("Retry", action: retry)
            .buttonStyle(.borderedProminent)
    }
    .padding()
}

}

Networking with APIService

API Service Pattern

// Services/APIService.swift import Foundation

enum APIError: LocalizedError { case invalidURL case invalidResponse case unauthorized case serverError(String) case decodingError

var errorDescription: String? {
    switch self {
    case .invalidURL: return "Invalid URL"
    case .invalidResponse: return "Invalid response from server"
    case .unauthorized: return "Please log in again"
    case .serverError(let message): return message
    case .decodingError: return "Failed to process server response"
    }
}

}

class APIService { static let shared = APIService()

private let baseURL: String
private let session: URLSession

init(baseURL: String = "https://api.example.com",
     session: URLSession = .shared) {
    self.baseURL = baseURL
    self.session = session
}

// MARK: - Generic Request Method

private func request&#x3C;T: Decodable>(
    _ endpoint: String,
    method: HTTPMethod = .get,
    body: (any Encodable)? = nil,
    headers: [String: String] = [:]
) async throws -> T {
    guard let url = URL(string: baseURL + endpoint) else {
        throw APIError.invalidURL
    }

    var request = URLRequest(url: url)
    request.httpMethod = method.rawValue
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")

    // Add authentication token
    if let token = await AuthService.shared.getToken() {
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    }

    // Add custom headers
    for (key, value) in headers {
        request.setValue(value, forHTTPHeaderField: key)
    }

    // Add body
    if let body = body {
        request.httpBody = try JSONEncoder().encode(body)
    }

    let (data, response) = try await session.data(for: request)

    guard let httpResponse = response as? HTTPURLResponse else {
        throw APIError.invalidResponse
    }

    switch httpResponse.statusCode {
    case 200...299:
        do {
            return try JSONDecoder().decode(T.self, from: data)
        } catch {
            print("Decoding error: \(error)")
            throw APIError.decodingError
        }

    case 401:
        throw APIError.unauthorized

    default:
        if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: data) {
            throw APIError.serverError(errorResponse.error.message)
        }
        throw APIError.serverError("Request failed with status \(httpResponse.statusCode)")
    }
}

// MARK: - API Methods

func fetchUsers() async throws -> [User] {
    let response: APIResponse&#x3C;[User]> = try await request("/api/users")
    return response.data
}

func createUser(_ user: CreateUserRequest) async throws -> User {
    let response: APIResponse&#x3C;User> = try await request(
        "/api/users",
        method: .post,
        body: user
    )
    return response.data
}

func updateUser(id: String, _ user: UpdateUserRequest) async throws -> User {
    let response: APIResponse&#x3C;User> = try await request(
        "/api/users/\(id)",
        method: .patch,
        body: user
    )
    return response.data
}

func deleteUser(id: String) async throws {
    let _: EmptyResponse = try await request(
        "/api/users/\(id)",
        method: .delete
    )
}

}

// MARK: - Supporting Types

enum HTTPMethod: String { case get = "GET" case post = "POST" case put = "PUT" case patch = "PATCH" case delete = "DELETE" }

struct EmptyResponse: Decodable {}

struct CreateUserRequest: Encodable { let name: String let email: String }

struct UpdateUserRequest: Encodable { let name: String? let email: String? }

Configuration Management

// Config/APIConfig.swift import Foundation

enum Environment { case development case staging case production

var baseURL: String {
    switch self {
    case .development: return "http://localhost:3000"
    case .staging: return "https://staging-api.example.com"
    case .production: return "https://api.example.com"
    }
}

}

struct APIConfig { static let environment: Environment = { #if DEBUG return .development #else return .production #endif }()

static let baseURL = environment.baseURL

}

SwiftUI Best Practices

View Composition

// Break down large views into smaller components struct ProductCard: View { let product: Product

var body: some View {
    VStack(alignment: .leading, spacing: 8) {
        ProductImage(url: product.imageURL)
        ProductInfo(product: product)
        ProductPrice(price: product.price)
    }
    .padding()
    .background(.background)
    .cornerRadius(12)
    .shadow(radius: 2)
}

}

// Use ViewBuilders for complex logic @ViewBuilder func statusBadge(for status: Status) -> some View { switch status { case .active: Label("Active", systemImage: "checkmark.circle.fill") .foregroundStyle(.green) case .pending: Label("Pending", systemImage: "clock.fill") .foregroundStyle(.orange) case .inactive: Label("Inactive", systemImage: "xmark.circle.fill") .foregroundStyle(.gray) } }

State Management

// @State for local view state @State private var isSheetPresented = false @State private var searchText = ""

// @StateObject for view model ownership @StateObject private var viewModel = MyViewModel()

// @ObservedObject for passed-in view models @ObservedObject var viewModel: MyViewModel

// @EnvironmentObject for app-wide state @EnvironmentObject var appState: AppState

// @AppStorage for UserDefaults @AppStorage("hasSeenOnboarding") private var hasSeenOnboarding = false

Performance Optimization

// Use Identifiable for list performance struct Item: Identifiable { let id: UUID let name: String }

// Lazy loading for large lists LazyVStack { ForEach(items) { item in ItemRow(item: item) } }

// Task lifecycle .task { // Runs when view appears, cancels when disappears await viewModel.load() }

// Prevent unnecessary redraws .equatable() // Only redraw when properties change

Error Handling Patterns

// Custom error types enum ValidationError: LocalizedError { case emptyField(String) case invalidEmail case passwordTooShort

var errorDescription: String? {
    switch self {
    case .emptyField(let field):
        return "\(field) cannot be empty"
    case .invalidEmail:
        return "Please enter a valid email address"
    case .passwordTooShort:
        return "Password must be at least 8 characters"
    }
}

}

// Error display in UI struct FormView: View { @State private var errorMessage: String?

var body: some View {
    Form {
        // Form fields...

        if let error = errorMessage {
            Text(error)
                .foregroundStyle(.red)
                .font(.caption)
        }
    }
}

}

Testing Patterns

Unit Testing ViewModels

import XCTest @testable import YourApp

@MainActor class UsersViewModelTests: XCTestCase { var viewModel: UsersViewModel! var mockAPIService: MockAPIService!

override func setUp() {
    super.setUp()
    mockAPIService = MockAPIService()
    viewModel = UsersViewModel(apiService: mockAPIService)
}

func testFetchUsersSuccess() async {
    // Given
    let expectedUsers = [
        User(id: "1", name: "John", email: "john@example.com", createdAt: Date())
    ]
    mockAPIService.usersToReturn = expectedUsers

    // When
    await viewModel.fetchUsers()

    // Then
    XCTAssertEqual(viewModel.users, expectedUsers)
    XCTAssertNil(viewModel.errorMessage)
    XCTAssertFalse(viewModel.isLoading)
}

func testFetchUsersFailure() async {
    // Given
    mockAPIService.shouldThrowError = true

    // When
    await viewModel.fetchUsers()

    // Then
    XCTAssertTrue(viewModel.users.isEmpty)
    XCTAssertNotNil(viewModel.errorMessage)
    XCTAssertFalse(viewModel.isLoading)
}

}

class MockAPIService: APIService { var usersToReturn: [User] = [] var shouldThrowError = false

override func fetchUsers() async throws -> [User] {
    if shouldThrowError {
        throw APIError.serverError("Mock error")
    }
    return usersToReturn
}

}

Code Organization Best Practices

  • One model/view/viewmodel per file

  • Group related files in folders

  • Use extensions to organize code by functionality

  • Keep files under 300 lines

  • Use meaningful variable names

  • Add comments only when logic isn't obvious

Swift Naming Conventions

// Types: UpperCamelCase class UserManager { } struct Product { } enum Status { }

// Variables, functions: lowerCamelCase var userName: String func fetchUserData() { }

// Constants: lowerCamelCase let maxRetryCount = 3

// Private properties: prefix with underscore (optional) private var _internalState: Int

// Boolean names: use is/has/should prefix var isLoading: Bool var hasSeenOnboarding: Bool func shouldShowAlert() -> Bool

Common SwiftUI Modifiers

// Layout .padding() .frame(width: 100, height: 50) .background(.blue) .cornerRadius(8)

// Navigation .navigationTitle("Title") .navigationBarTitleDisplayMode(.inline) .toolbar { }

// Presentation .sheet(isPresented: $showSheet) { } .alert("Title", isPresented: $showAlert) { }

// Lists .listStyle(.insetGrouped) .swipeActions { } .onDelete { }

// Performance .task { } .refreshable { }

Security Best Practices

  • Never hardcode API keys or secrets

  • Use Keychain for sensitive data

  • Validate all user input

  • Use HTTPS for all network requests

  • Implement certificate pinning for production

  • Obfuscate sensitive data in logs

  • Handle authentication token expiry

Quick Reference

Run iOS app

Cmd+R in Xcode

Run tests

Cmd+U in Xcode

Clean build

Cmd+Shift+K

Format code

Ctrl+I

Additional Resources

  • Swift Documentation

  • SwiftUI Documentation

  • Human Interface Guidelines

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

apply

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

Workspace Init

Use this skill to initialize or update a multi-repo workspace created from dev-config-template. Invoke whenever the user wants to: set up a fresh workspace c...

Registry SourceRecently Updated
Coding

Google Seo Assistant

A client-facing SEO assistant grounded in Google's official SEO Starter Guide. Use this skill whenever a user mentions SEO, search rankings, Google visibilit...

Registry SourceRecently Updated