Swift Coding Conventions is a development Claude Skill built by Heiko Panjas. Best for: Swift developers reviewing, writing, or refactoring code use this to maintain consistency with Swift 5.9+ conventions..

What it does
Enforce Swift naming, structure, and access control standards for consistent, modern code across teams.
Category
development
Created by
Heiko Panjas
Last updated
developmentintermediate

Swift Coding Conventions

Enforce Swift naming, structure, and access control standards for consistent, modern code across teams.

Skill instructions


name: swift-conventions description: Swift coding conventions and best practices for modern Swift development. Use when writing, reviewing, or refactoring Swift code to ensure consistency with naming conventions, access control, async/await patterns, and SwiftUI/framework best practices. compatibility: Swift 5.9+

Swift Coding Conventions

Naming Conventions

Types (Classes, Structs, Enums, Protocols)

Use PascalCase for all type names:

public class ProcessManager { }
public struct Location { }
public enum ProcessQuality { }
public protocol ProcessController { }

Properties & Variables

Use camelCase for properties, variables, and instance names:

let locationManager = LocationManager()
var subscriptions: [ProcessSubscription] = []
private let updateInterval: TimeInterval = 60

Functions & Methods

Use camelCase with descriptive action verbs:

func refreshData(for location: Location) async throws -> ProcessSensor?
func updateLocation(location: Location)
private func significantLocationChange(previous: Location?, current: Location) -> Bool

Constants

Use camelCase for constant properties, UPPER_SNAKE_CASE for macro-like constants:

public static let houseOfWorldCultures = Location(latitude: 52.51889, longitude: 13.36528)
private let updateInterval: TimeInterval = 60

Enums

Use PascalCase for enum name, camelCase for cases:

public enum ProcessQuality {
    case good
    case uncertain
    case bad
}

public enum ProcessSelector: Hashable {
    case weather(Weather)
    case forecast(Forecast)
}

Code Structure

Braces

Opening brace on same line, closing brace on new line:

public class ProcessManager {
    func updateSubscriptions() {
        for subscription in subscriptions {
            subscription.update(timeout: updateInterval)
        }
    }
}

Indentation

  • Use 4 spaces (no tabs)
  • Align continuation lines with opening delimiter

Line Length

Target 120 characters maximum per line. Break at logical points:

public func dataWithRetry(
    from url: URL, retryCount: Int = 3, retryInterval: TimeInterval = 1.0
) async throws -> (Data, URLResponse) {
    // Implementation
}

Access Control

Always explicit: Mark APIs as public intentionally; avoid relying on default internal.

public class ProcessManager: Identifiable {
    public let id = UUID()
    public static let shared = ProcessManager()
    private let locationManager = LocationManager()
    private var location: Location?
}

Order of access levels (most to least restrictive):

  1. private - Only within current declaration
  2. fileprivate - Only within same source file
  3. internal - Within module (default)
  4. public - Visible to consumers
  5. open - Visible and subclassable (use rarely)

Type Declarations

Classes

public class ProcessManager: Identifiable, LocationManagerDelegate {
    public let id = UUID()
    public static let shared = ProcessManager()
    private init() { }
}

Structs

public struct Location: Equatable, Hashable {
    public let latitude: Double
    public let longitude: Double

    public init(latitude: Double, longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
    }
}

Enums

public enum ProcessQuality {
    case good
    case uncertain
    case bad
    case unknown
}

public enum Weather: Int, CaseIterable {
    case temperature = 0
    case apparentTemperature = 1
}

public enum ProcessSelector: Hashable {
    case weather(Weather)
    case forecast(Forecast)
    case covid(Covid)
}

Protocols

public protocol ProcessController {
    func refreshData(for location: Location) async throws -> ProcessSensor?
}

public protocol LocationManagerDelegate: Identifiable where ID == UUID {
    func locationManager(didUpdateLocation location: Location)
}

Properties

Stored Properties

public let id = UUID()
private let locationManager = LocationManager()
private var location: Location?
private let updateInterval: TimeInterval = 60

Computed Properties

public var coordinate: CLLocationCoordinate2D {
    return CLLocationCoordinate2D(latitude: self.latitude, longitude: self.longitude)
}

var isReady: Bool {
    return location != nil && subscriptions.isEmpty == false
}

Property Observers

var location: Location? {
    willSet {
        print("About to set location")
    }
    didSet {
        if location != oldValue {
            refreshSubscriptions()
        }
    }
}

Lazy Properties

lazy var dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
    return formatter
}()

Functions & Methods

Basic Structure

public func refreshData(for location: Location) async throws -> ProcessSensor? {
    let weather = try await WeatherService.shared.weather(for: clLocation)
    return ProcessSensor(name: "", location: location, measurements: [:], timestamp: Date.now)
}

Parameter Labels

Descriptive external labels; omit with underscore when appropriate:

func updateLocation(location: Location)
func add(subscriber: any ProcessSubscriber, timeout: TimeInterval)
func process(_ data: Data) -> Result

Default Parameters

Place at end of parameter list:

public func dataWithRetry(
    from url: URL,
    retryCount: Int = 3,
    retryInterval: TimeInterval = 1.0
) async throws -> (Data, URLResponse) {
    // Implementation
}

Multiple Initializers

Designated initializer with convenience initializers:

public init(value: Measurement<T>, customData: [String: Any]?, quality: ProcessQuality, timestamp: Date) {
    self.value = value
    self.customData = customData
    self.quality = quality
    self.timestamp = timestamp
}

public convenience init(value: Measurement<T>, quality: ProcessQuality) {
    self.init(value: value, customData: nil, quality: quality, timestamp: Date.now)
}

Control Flow

If Statements

if location != nil {
    refreshSubscriptions()
}

if let location = self.location {
    delegate.locationManager(didUpdateLocation: location)
}

Guard Statements

guard let location = self.location else {
    return
}

guard let data = data,
      let response = response as? HTTPURLResponse,
      (200...299).contains(response.statusCode) else {
    throw NetworkError.invalidResponse
}

For Loops

for subscription in subscriptions {
    subscription.update(timeout: updateInterval)
}

for (index, item) in items.enumerated() {
    print("\(index): \(item)")
}

for subscription in subscriptions where subscription.isPending() {
    subscription.reset()
}

Switch Statements

switch quality {
    case .good:
        return "✓"
    case .uncertain:
        return "~"
    case .bad:
        return "✗"
    case .unknown:
        return "?"
}

switch connectionType {
    case .wifi, .ethernet:
        return true
    case .cellular:
        return false
}

Error Handling

Error Definitions

enum NetworkError: Error {
    case invalidResponse
    case serverError(statusCode: Int)
    case noData
}

Throwing Functions

public func refreshData(for location: Location) async throws -> ProcessSensor? {
    let weather = try await WeatherService.shared.weather(for: clLocation)
    return sensor
}

Try-Catch Blocks

do {
    let (data, response) = try await self.data(from: url)
    return (data, response)
} catch NetworkError.invalidResponse {
    return nil
} catch {
    print("Error: \(error)")
    return nil
}

Optional Try

if let placemark = try? await geocoder.reverseGeocodeLocation(location).first {
    // Use placemark
}

let config = try! Configuration.load()  // Only if guaranteed to succeed

Async/Await

Async Functions

public func refreshData(for location: Location) async throws -> ProcessSensor? {
    let weather = try await WeatherService.shared.weather(for: clLocation)
    let placemark = await LocationManager.reverseGeocodeLocation(location: location)
    return ProcessSensor(/* ... */)
}

Task Creation

Task {
    await delegate.refreshData(location: location)
}

Task {
    do {
        let result = try await fetchData()
        process(result)
    } catch {
        print("Error: \(error)")
    }
}

Actor Usage

actor NetworkManager {
    private var isConnected = true

    func updateConnectionStatus(_ status: Bool) {
        self.isConnected = status
    }
}

Sendable Conformance

public class UnitRadiation: Dimension, @unchecked Sendable {
    public static let sieverts = UnitRadiation(
        symbol: "Sv/h",
        converter: UnitConverterLinear(coefficient: 1.0)
    )
}

Protocols & Extensions

Protocol Conformance

public class ProcessManager: Identifiable, LocationManagerDelegate {
    // Implementation
}

extension ProcessManager: CustomStringConvertible {
    public var description: String {
        return "ProcessManager with \(subscriptions.count) subscriptions"
    }
}

Extension Organization

Use MARK comments to organize extensions by purpose:

// MARK: - LocationManagerDelegate
extension ProcessManager: LocationManagerDelegate {
    public func locationManager(didUpdateLocation location: Location) {
        // Implementation
    }
}

// MARK: - Subscription Management
extension ProcessManager {
    public func add(subscriber: any ProcessSubscriber, timeout: TimeInterval) {
        // Implementation
    }
}

Generics

Generic Types

public struct ProcessValue<T: Dimension>: Identifiable {
    public let id = UUID()
    public let value: Measurement<T>
    public let quality: ProcessQuality
}

Generic Functions

func measure<T: Dimension>(_ value: Double, unit: T) -> Measurement<T> {
    return Measurement(value: value, unit: unit)
}

Type Erasure

private var subscribers: [UUID: any ProcessSubscriber] = [:]

public func add(subscriber: any ProcessSubscriber, timeout: TimeInterval) {
    subscribers[subscriber.id] = subscriber
}

Comments & Documentation

Single-Line Comments

Comment the "why", not the "what":

// Check if device is connected before attempting network request
guard ReachabilityManager.shared.isConnected else {
    throw URLError(.notConnectedToInternet)
}

Documentation Comments

Use DocC-style documentation for public APIs:

/// A simple and fast logging facility with support for different log levels.
public class Trace {
    /// Creates a new Logger instance
    /// - Parameters:
    ///   - minimumLevel: Minimum level of logs to display
    ///   - showColors: Whether to use ANSI colors
    ///   - dateFormat: Format string for timestamps
    public init(
        minimumLevel: Level = .debug,
        showColors: Bool = true,
        dateFormat: String = "yyyy-MM-dd HH:mm:ss.SSS"
    ) {
        // Implementation
    }
}

MARK Comments

public class WeatherController {
    // MARK: - Properties
    private let service = WeatherService.shared

    // MARK: - Initialization
    public init() { }

    // MARK: - Public Methods
    public func refreshData(for location: Location) async throws -> ProcessSensor? {
        // Implementation
    }

    // MARK: - Private Helpers
    private func processWeatherData(_ data: WeatherData) -> ProcessSensor {
        // Implementation
    }
}

Formatting & Whitespace

Blank Lines

Separate logical sections with blank lines:

public class ProcessManager {
    public let id = UUID()
    public static let shared = ProcessManager()

    private let locationManager = LocationManager()
    private var location: Location?

    public func refreshSubscriptions() {
        // Implementation
    }
}

Spacing

  • Space after comma: [1, 2, 3]
  • Space around operators: a + b
  • No space before colon, space after: var dict: [String: Int] = [:]
  • No space around range operators: for i in 0..<count, 0...10

Trailing Whitespace

Remove all trailing whitespace at end of lines.

Swift-Specific Patterns

Optionals

if let location = self.location {
    process(location)
}

guard let location = self.location else {
    return
}

let count = subscribers[id]?.subscriptions.count
let value = optionalValue ?? defaultValue

Type Inference

let manager = ProcessManager.shared
let id = UUID()
let values = [1, 2, 3]

let timeout: TimeInterval = 60  // Explicit when needed

Closures

Timer.scheduledTimer(withTimeInterval: updateInterval, repeats: true) { _ in
    self.updateSubscriptions()
}

items.map { $0.value * 2 }

UIView.animate(withDuration: 0.3) {
    view.alpha = 0
} completion: { _ in
    view.removeFromSuperview()
}

Collections

var subscriptions: [ProcessSubscription] = []
var measurements: [ProcessSelector: [ProcessValue<Dimension>]] = [:]
let uniqueIds: Set<UUID> = []

Property Wrappers

@Published var measurements: [ProcessValue<Dimension>] = []
@AppStorage("refreshInterval") var refreshInterval: TimeInterval = 60

Code Review Checklist

  • [ ] All public APIs have explicit public access control
  • [ ] All types follow naming conventions (PascalCase)
  • [ ] All functions/properties follow naming conventions (camelCase)
  • [ ] Code is formatted with 4-space indentation
  • [ ] Documentation comments for public APIs
  • [ ] Error handling is comprehensive
  • [ ] Async/await used consistently
  • [ ] No platform-specific UI dependencies in libraries
  • [ ] Custom Dimension types conform to @unchecked Sendable
  • [ ] Protocol conformance is explicit
  • [ ] MARK comments organize code sections
  • [ ] No force unwrapping (!) unless absolutely safe
  • [ ] CRITICAL: Always verify documentation against actual implementation

Architectural Patterns

Controller Pattern

public class WeatherController: ProcessController {
    public func refreshData(for location: Location) async throws -> ProcessSensor? {
        // Fetch data from service
        // Process into ProcessSensor
        // Return structured data
    }
}

Service Pattern

public class CovidService {
    static func fetchDistricts(for location: Location, radius: Double) async throws -> Data? {
        // Perform HTTP request
        // Return raw data
    }
}

Transformer Pattern

public class WeatherTransformer: ProcessTransformer {
    override public func renderCurrent(measurements: [ProcessSelector: [ProcessValue<Dimension>]])
        -> [ProcessSelector: ProcessValue<Dimension>] {
        // Transform measurements into current values
    }
}

Data Flow

Service (HTTP) → Controller (Parse) → Transformer (Process) → Consumer (Display)

Install

/plugin install swift-coding-conventions@heikopanjas

Requires Claude Code CLI.

Use cases

Swift developers reviewing, writing, or refactoring code use this to maintain consistency with Swift 5.9+ conventions.

Reviews

No reviews yet. Be the first to review this skill.

Stats

Installs0
GitHub Stars0
Forks0
LicenseMIT License
UpdatedFeb 26, 2026