Skip to main content

Swift & SwiftUI Language-Specific Standards

Based on Swift API Design Guidelines, SwiftUI Best Practices, and Apple Human Interface Guidelines

Overview

This document provides comprehensive Swift and SwiftUI coding standards including:

  • Swift API Design Guidelines - Apple's official conventions
  • SwiftUI Best Practices - Modern declarative UI patterns
  • Effective Swift - Idiomatic Swift patterns
  • Concurrency - async/await and structured concurrency
  • Combine - Reactive programming (when needed)

Formatting Standards

Indentation

Standard: 4 spaces (no tabs)

Follows Apple's Swift conventions and Xcode defaults.

Brace Style

K&R style - Opening brace on same line:

func calculateTotal(items: [Item]) -> Decimal {
return items.reduce(0) { $0 + $1.price }
}

if condition {
doSomething()
} else {
doSomethingElse()
}
```text

### Line Length
- **Soft limit:** 100 characters
- **Hard limit:** 120 characters
- Break long lines at natural boundaries

### Trailing Commas
Use trailing commas in multiline arrays, dictionaries, and parameter lists:
```swift
let colors = [
.red,
.blue,
.green, // ✅ Trailing comma
]

func configure(
name: String,
age: Int,
address: String // ✅ Trailing comma
) {
// ...
}
```text

---

## Naming Conventions

### Types and Protocols
**UpperCamelCase (PascalCase)**
```swift
class UserViewModel { }
struct UserProfile { }
enum LoadingState { }
protocol Identifiable { }
```text

### Functions and Properties
**lowerCamelCase**
```swift
func fetchUserData() { }
var userName: String
let itemCount: Int
```text

### Constants
**lowerCamelCase** (not SCREAMING_SNAKE_CASE)
```swift
// ✅ Swift style
let maximumLoginAttempts = 3
let defaultTimeout: TimeInterval = 30

// ❌ Don't use
let MAXIMUM_LOGIN_ATTEMPTS = 3
```text

### Boolean Properties and Functions
Use clear, affirmative names:
```swift
// ✅ Good
var isLoading: Bool
var hasUnsavedChanges: Bool
func canSubmit() -> Bool

// ❌ Avoid negatives
var isNotReady: Bool // Prefer: isReady
```text

### Protocol Naming
- **Capability:** `-able`, `-ible` suffix
- **Description:** noun
```swift
protocol Drawable { } // Capability
protocol DataSource { } // Description
protocol ViewModelProtocol { } // ❌ Avoid redundant "Protocol"
```text

---

## Swift Best Practices

### 1. Type Inference
Let the compiler infer types when obvious:
```swift
// ✅ Good
let name = "John"
let count = items.count
let colors = [.red, .blue, .green]

// ❌ Redundant
let name: String = "John"
let count: Int = items.count
```text

### 2. Optionals
**Use guard for early returns:**
```swift
// ✅ Good
guard let user = currentUser else {
return
}
// Use user safely here

// ❌ Avoid deep nesting
if let user = currentUser {
// Deep nesting...
}
```text

**Use nil coalescing and optional chaining:**
```swift
// ✅ Good
let userName = user?.name ?? "Guest"
let count = users?.count ?? 0

// ❌ Verbose
let userName: String
if let user = user, let name = user.name {
userName = name
} else {
userName = "Guest"
}
```text

### 3. Collections
**Use higher-order functions:**
```swift
// ✅ Good
let adults = users.filter { $0.age >= 18 }
let names = users.map { $0.name }
let total = prices.reduce(0, +)

// ❌ Imperative style
var adults: [User] = []
for user in users {
if user.age >= 18 {
adults.append(user)
}
}
```text

### 4. Error Handling
**Use proper error handling:**
```swift
// ✅ Good
enum NetworkError: Error {
case invalidURL
case requestFailed(Error)
case invalidResponse
}

func fetchData() async throws -> Data {
guard let url = URL(string: endpoint) else {
throw NetworkError.invalidURL
}

let (data, response) = try await URLSession.shared.data(from: url)

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

return data
}

// Usage
do {
let data = try await fetchData()
// Process data
} catch NetworkError.invalidURL {
// Handle specific error
} catch {
// Handle general error
}
```text

### 5. Computed Properties vs Functions
**Use computed properties for lightweight, side-effect-free values:**
```swift
// ✅ Computed property (no side effects, cheap)
var fullName: String {
"\(firstName) \(lastName)"
}

// ✅ Function (has side effects or expensive)
func fetchUserData() async throws -> User {
// Network call
}
```text

### 6. Access Control
**Use explicit access control:**
```swift
// ✅ Good
public class ViewModel { }
private let cache = Cache()
fileprivate func helper() { }

// Default is internal, but be explicit when it matters
```text

### 7. Extensions for Organization
**Group related functionality:**
```swift
// MARK: - Initialization
extension UserViewModel {
convenience init(userId: String) {
self.init()
self.userId = userId
}
}

// MARK: - Public Methods
extension UserViewModel {
func loadUser() async {
// ...
}
}

// MARK: - Private Helpers
private extension UserViewModel {
func validateEmail(_ email: String) -> Bool {
// ...
}
}
```text

---

## SwiftUI Best Practices

### 1. View Composition
**Break complex views into smaller components:**
```swift
// ✅ Good - Composed
struct UserProfileView: View {
let user: User

var body: some View {
VStack(spacing: 16) {
ProfileHeaderView(user: user)
ProfileDetailsView(user: user)
ProfileActionsView(user: user)
}
}
}

// ❌ Bad - Monolithic
struct UserProfileView: View {
let user: User

var body: some View {
VStack {
// 200 lines of UI code...
}
}
}
```text

### 2. View Modifiers
**Extract custom modifiers for reusability:**
```swift
// Define custom modifier
struct CardStyle: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(Color.white)
.cornerRadius(12)
.shadow(radius: 4)
}
}

extension View {
func cardStyle() -> some View {
modifier(CardStyle())
}
}

// Usage
Text("Hello")
.cardStyle()
```text

### 3. State Management
**Follow single source of truth:**
```swift
// ✅ Good
@StateObject private var viewModel = UserViewModel()

struct ContentView: View {
@StateObject private var viewModel = UserViewModel()

var body: some View {
UserListView(users: viewModel.users)
.task {
await viewModel.loadUsers()
}
}
}

// Child view
struct UserListView: View {
let users: [User] // ✅ Pass data down

var body: some View {
List(users) { user in
Text(user.name)
}
}
}
```text

**Use appropriate property wrappers:**
```swift
@State // View-local state
@StateObject // Own the object lifetime
@ObservedObject // Observe externally-owned object
@EnvironmentObject // Shared across view hierarchy
@Binding // Two-way binding to parent state
@AppStorage // UserDefaults-backed storage
@Published // Observable property in ObservableObject
```text

### 4. Async Operations
**Use .task modifier for async work:**
```swift
// ✅ Good
struct ContentView: View {
@StateObject private var viewModel = ViewModel()

var body: some View {
List(viewModel.items) { item in
Text(item.name)
}
.task {
await viewModel.loadItems()
}
.refreshable {
await viewModel.refresh()
}
}
}

// ❌ Avoid .onAppear with Task
.onAppear {
Task { // Manual task management
await viewModel.loadItems()
}
}
```text

### 5. Preview Provider
**Provide comprehensive previews:**
```swift
struct UserView_Previews: PreviewProvider {
static var previews: some View {
Group {
// Default state
UserView(user: .sample)
.previewDisplayName("Default")

// Empty state
UserView(user: .empty)
.previewDisplayName("Empty")

// Dark mode
UserView(user: .sample)
.preferredColorScheme(.dark)
.previewDisplayName("Dark Mode")

// Different sizes
UserView(user: .sample)
.previewLayout(.sizeThatFits)
.previewDisplayName("Size That Fits")
}
}
}

// Sample data extension
extension User {
static let sample = User(
name: "John Doe",
email: "john@example.com"
)

static let empty = User(name: "", email: "")
}
```text

### 6. Accessibility
**Always include accessibility support:**
```swift
struct UserRowView: View {
let user: User

var body: some View {
HStack {
Image(systemName: "person.circle")
.accessibilityHidden(true) // Decorative

VStack(alignment: .leading) {
Text(user.name)
.font(.headline)
Text(user.email)
.font(.caption)
.foregroundColor(.secondary)
}
}
.accessibilityElement(children: .combine)
.accessibilityLabel("\(user.name), \(user.email)")
}
}
```text

### 7. Performance
**Use @ViewBuilder for conditional views:**
```swift
// ✅ Good
@ViewBuilder
var statusView: some View {
switch status {
case .loading:
ProgressView()
case .loaded(let data):
DataView(data: data)
case .error(let message):
ErrorView(message: message)
}
}

// Use .id() to force view recreation when needed
List(items, id: \.id) { item in
ItemRow(item: item)
}
.id(refreshID) // Change ID to recreate view
```text

---

## Modern Concurrency (async/await)

### Async Functions
```swift
// ✅ Good
func fetchUser(id: String) async throws -> User {
let url = URL(string: "https://api.example.com/users/\(id)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}

// Multiple concurrent operations
func loadData() async {
async let users = fetchUsers()
async let posts = fetchPosts()
async let comments = fetchComments()

let (userList, postList, commentList) = await (users, posts, comments)
// Process all data
}
```text

### Task Groups
```swift
func fetchAllUserData(userIDs: [String]) async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
for id in userIDs {
group.addTask {
try await fetchUser(id: id)
}
}

var users: [User] = []
for try await user in group {
users.append(user)
}
return users
}
}
```text

### Actors
```swift
// ✅ Use actors for thread-safe mutable state
actor UserCache {
private var cache: [String: User] = [:]

func get(_ id: String) -> User? {
cache[id]
}

func set(_ user: User, for id: String) {
cache[id] = user
}
}

// Usage
let cache = UserCache()
await cache.set(user, for: user.id)
let cachedUser = await cache.get(user.id)
```text

---

## Testing Best Practices

### Unit Tests
```swift
import XCTest
@testable import MyApp

final class UserViewModelTests: XCTestCase {
var sut: UserViewModel!
var mockRepository: MockUserRepository!

override func setUp() {
super.setUp()
mockRepository = MockUserRepository()
sut = UserViewModel(repository: mockRepository)
}

override func tearDown() {
sut = nil
mockRepository = nil
super.tearDown()
}

func testLoadUsers_whenSuccessful_updatesUsers() async {
// Given
let expectedUsers = [User.sample]
mockRepository.users = expectedUsers

// When
await sut.loadUsers()

// Then
XCTAssertEqual(sut.users, expectedUsers)
XCTAssertFalse(sut.isLoading)
}

func testLoadUsers_whenFails_setsError() async {
// Given
mockRepository.shouldFail = true

// When
await sut.loadUsers()

// Then
XCTAssertNotNil(sut.error)
XCTAssertTrue(sut.users.isEmpty)
}
}
```text

### UI Tests
```swift
import XCTest

final class UserFlowUITests: XCTestCase {
let app = XCUIApplication()

override func setUpWithError() throws {
continueAfterFailure = false
app.launch()
}

func testUserLoginFlow() throws {
// Given
let emailField = app.textFields["email"]
let passwordField = app.secureTextFields["password"]
let loginButton = app.buttons["login"]

// When
emailField.tap()
emailField.typeText("test@example.com")

passwordField.tap()
passwordField.typeText("password123")

loginButton.tap()

// Then
let welcomeText = app.staticTexts["Welcome"]
XCTAssertTrue(welcomeText.waitForExistence(timeout: 5))
}
}
```text

---

## Common Anti-Patterns to Avoid

### ❌ Force Unwrapping
```swift
// ❌ Bad
let user = users.first! // Crashes if empty

// ✅ Good
guard let user = users.first else { return }
```text

### ❌ Pyramid of Doom
```swift
// ❌ Bad
if let a = optionalA {
if let b = optionalB {
if let c = optionalC {
// Deep nesting
}
}
}

// ✅ Good
guard let a = optionalA,
let b = optionalB,
let c = optionalC else {
return
}
// Flat code
```text

### ❌ Massive View Controllers/ViewModels
```swift
// ❌ Bad - 1000+ lines
class UserViewModel: ObservableObject {
// Everything user-related
}

// ✅ Good - Split responsibilities
class UserProfileViewModel: ObservableObject { }
class UserSettingsViewModel: ObservableObject { }
class UserPostsViewModel: ObservableObject { }
```text

### ❌ Implicit Self in Closures
```swift
// ❌ Bad - potential retain cycles
class ViewModel: ObservableObject {
func loadData() {
service.fetch { data in
self.data = data // Strong reference
}
}
}

// ✅ Good - explicit capture
class ViewModel: ObservableObject {
func loadData() {
service.fetch { [weak self] data in
self?.data = data
}
}
}
```text

---

## Code Review Checklist

### General
- [ ] Follows Swift API Design Guidelines
- [ ] Proper access control (private, fileprivate, internal, public)
- [ ] No force unwrapping (!)
- [ ] Proper error handling
- [ ] No retain cycles in closures

### SwiftUI Specific
- [ ] Views are composable and reusable
- [ ] Proper property wrappers (@State, @Binding, etc.)
- [ ] Accessibility labels and hints provided
- [ ] Preview providers included
- [ ] .task used for async operations

### Testing
- [ ] Unit tests for business logic
- [ ] UI tests for critical flows
- [ ] Tests follow AAA pattern (Arrange, Act, Assert)
- [ ] Mock/stub dependencies properly

### Performance
- [ ] No unnecessary view redraws
- [ ] Lazy loading for expensive operations
- [ ] Proper use of .id() for list performance
- [ ] Async operations properly structured

---

## Tools and Linters

### SwiftLint
Use SwiftLint for automated style checking:
```yaml
# .swiftlint.yml
disabled_rules:
- trailing_whitespace
opt_in_rules:
- empty_count
- explicit_init
line_length: 120
```text

### SwiftFormat
Use SwiftFormat for consistent formatting:
```text
--indent 4
--maxwidth 120
--wraparguments before-first
--wrapcollections before-first
```text

---

## Resources

- [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/)
- [SwiftUI Documentation](https://developer.apple.com/documentation/swiftui/)
- [Swift.org Documentation](https://swift.org/documentation/)
- [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/)
- [Effective Swift](https://www.swift.org/documentation/)

---

**Follow these standards for clean, maintainable, and idiomatic Swift/SwiftUI code.**