ios-development
iOS Development Guidelines
This skill provides comprehensive guidance for building high-quality iOS applications using Swift, SwiftUI, and UIKit.
Part 1: Swift Best Practices
Modern Swift Conventions
// PREFER: Use guard for early exits
func processUser(_ user: User?) {
guard let user = user else { return }
// Process user...
}
// PREFER: Use if-let for optional binding
if let name = user.name {
print(name)
}
// PREFER: Trailing closure syntax
users.filter { $0.isActive }
.map { $0.name }
// PREFER: Computed properties over methods for simple getters
var fullName: String {
"\(firstName) \(lastName)"
}
Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Types | UpperCamelCase | UserProfile, NetworkManager |
| Functions/Variables | lowerCamelCase | fetchUsers(), userName |
| Constants | lowerCamelCase | let maxRetryCount = 3 |
| Protocols | UpperCamelCase + able/ing/Type | Equatable, Loading, ViewModelType |
| Enums | UpperCamelCase, cases lowerCamelCase | enum State { case loading } |
Error Handling
// Define custom errors
enum NetworkError: LocalizedError {
case invalidURL
case noData
case decodingFailed(Error)
var errorDescription: String? {
switch self {
case .invalidURL: return "Invalid URL"
case .noData: return "No data received"
case .decodingFailed(let error): return "Decoding failed: \(error.localizedDescription)"
}
}
}
// Use Result type for async operations
func fetchData() async -> Result<Data, NetworkError> {
// ...
}
// Or throw errors with async/await
func fetchData() async throws -> Data {
// ...
}
Part 2: SwiftUI Patterns
View Structure
struct ContentView: View {
// MARK: - Properties
@StateObject private var viewModel = ContentViewModel()
@State private var isPresented = false
// MARK: - Body
var body: some View {
NavigationStack {
content
.navigationTitle("Home")
.toolbar { toolbarContent }
}
.sheet(isPresented: $isPresented) {
DetailView()
}
}
// MARK: - Subviews
private var content: some View {
List(viewModel.items) { item in
ItemRow(item: item)
}
}
@ToolbarContentBuilder
private var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .primaryAction) {
Button("Add") { isPresented = true }
}
}
}
State Management
| Property Wrapper | Use Case |
|---|---|
@State |
Simple value types owned by the view |
@Binding |
Two-way connection to parent's state |
@StateObject |
Reference type owned by the view (create once) |
@ObservedObject |
Reference type passed from parent |
@EnvironmentObject |
Shared data across view hierarchy |
@Environment |
System values (colorScheme, locale, etc.) |
Extracting Subviews
// GOOD: Extract complex subviews
struct ProfileView: View {
let user: User
var body: some View {
VStack {
ProfileHeader(user: user)
ProfileStats(user: user)
ProfileActions(user: user)
}
}
}
// GOOD: Use ViewBuilder for conditional content
@ViewBuilder
private var statusView: some View {
if isLoading {
ProgressView()
} else if let error = error {
ErrorView(error: error)
} else {
ContentView()
}
}
Part 3: Architecture Patterns
MVVM (Recommended for SwiftUI)
// Model
struct User: Identifiable, Codable {
let id: UUID
var name: String
var email: String
}
// ViewModel
@MainActor
class UserViewModel: ObservableObject {
@Published private(set) var users: [User] = []
@Published private(set) var isLoading = false
@Published var error: Error?
private let repository: UserRepositoryProtocol
init(repository: UserRepositoryProtocol = UserRepository()) {
self.repository = repository
}
func fetchUsers() async {
isLoading = true
defer { isLoading = false }
do {
users = try await repository.fetchUsers()
} catch {
self.error = error
}
}
}
// View
struct UserListView: View {
@StateObject private var viewModel = UserViewModel()
var body: some View {
List(viewModel.users) { user in
Text(user.name)
}
.task { await viewModel.fetchUsers() }
}
}
Repository Pattern
protocol UserRepositoryProtocol {
func fetchUsers() async throws -> [User]
func saveUser(_ user: User) async throws
}
class UserRepository: UserRepositoryProtocol {
private let networkService: NetworkServiceProtocol
private let cacheService: CacheServiceProtocol
init(
networkService: NetworkServiceProtocol = NetworkService(),
cacheService: CacheServiceProtocol = CacheService()
) {
self.networkService = networkService
self.cacheService = cacheService
}
func fetchUsers() async throws -> [User] {
// Check cache first
if let cached: [User] = cacheService.get(forKey: "users") {
return cached
}
// Fetch from network
let users = try await networkService.fetch([User].self, from: .users)
cacheService.set(users, forKey: "users")
return users
}
}
Part 4: Networking
Modern Async/Await Networking
class NetworkService {
private let session: URLSession
private let decoder: JSONDecoder
init(session: URLSession = .shared) {
self.session = session
self.decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
}
func fetch<T: Decodable>(_ type: T.Type, from endpoint: Endpoint) async throws -> T {
let request = try endpoint.urlRequest()
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
guard (200...299).contains(httpResponse.statusCode) else {
throw NetworkError.httpError(httpResponse.statusCode)
}
return try decoder.decode(T.self, from: data)
}
}
Endpoint Configuration
enum Endpoint {
case users
case user(id: UUID)
case createUser(User)
var path: String {
switch self {
case .users: return "/users"
case .user(let id): return "/users/\(id)"
case .createUser: return "/users"
}
}
var method: HTTPMethod {
switch self {
case .users, .user: return .get
case .createUser: return .post
}
}
func urlRequest() throws -> URLRequest {
guard let url = URL(string: APIConfig.baseURL + path) else {
throw NetworkError.invalidURL
}
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
if case .createUser(let user) = self {
request.httpBody = try JSONEncoder().encode(user)
}
return request
}
}
Part 5: Data Persistence
SwiftData (iOS 17+)
import SwiftData
@Model
class Task {
var title: String
var isCompleted: Bool
var createdAt: Date
init(title: String, isCompleted: Bool = false) {
self.title = title
self.isCompleted = isCompleted
self.createdAt = Date()
}
}
// In App
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: Task.self)
}
}
// In View
struct TaskListView: View {
@Environment(\.modelContext) private var modelContext
@Query(sort: \Task.createdAt) private var tasks: [Task]
var body: some View {
List(tasks) { task in
TaskRow(task: task)
}
}
func addTask(title: String) {
let task = Task(title: title)
modelContext.insert(task)
}
}
UserDefaults (Simple Settings)
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
set { UserDefaults.standard.set(newValue, forKey: key) }
}
}
// Usage
enum Settings {
@UserDefault(key: "hasCompletedOnboarding", defaultValue: false)
static var hasCompletedOnboarding: Bool
@UserDefault(key: "preferredTheme", defaultValue: "system")
static var preferredTheme: String
}
Part 6: Testing
Unit Testing
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 test_fetchUsers_success_updatesUsers() async {
// Given
let expectedUsers = [User(id: UUID(), name: "Test", email: "test@example.com")]
mockRepository.usersToReturn = expectedUsers
// When
await sut.fetchUsers()
// Then
XCTAssertEqual(sut.users, expectedUsers)
XCTAssertFalse(sut.isLoading)
XCTAssertNil(sut.error)
}
func test_fetchUsers_failure_setsError() async {
// Given
mockRepository.errorToThrow = NetworkError.noData
// When
await sut.fetchUsers()
// Then
XCTAssertTrue(sut.users.isEmpty)
XCTAssertNotNil(sut.error)
}
}
UI Testing
import XCTest
final class OnboardingUITests: XCTestCase {
let app = XCUIApplication()
override func setUp() {
continueAfterFailure = false
app.launchArguments = ["--uitesting"]
app.launch()
}
func test_onboarding_completeFlow() {
// First screen
XCTAssertTrue(app.staticTexts["Welcome"].exists)
app.buttons["Continue"].tap()
// Second screen
XCTAssertTrue(app.staticTexts["Features"].exists)
app.buttons["Get Started"].tap()
// Main app
XCTAssertTrue(app.navigationBars["Home"].exists)
}
}
Pre-Implementation Checklist
- Define clear architecture (MVVM recommended for SwiftUI)
- Set up dependency injection for testability
- Plan data models with Codable conformance
- Consider offline support requirements
- Plan error handling strategy
- Set up proper state management (@StateObject vs @ObservedObject)
- Plan navigation flow (NavigationStack, sheets, fullScreenCover)
- Consider accessibility (VoiceOver, Dynamic Type)
- Plan for different device sizes and orientations
Common Pitfalls to Avoid
| Issue | Problem | Solution |
|---|---|---|
| Massive Views | Hard to maintain, poor performance | Extract subviews, use ViewBuilder |
| Wrong property wrapper | Memory leaks, unexpected behavior | Use @StateObject for owned objects |
| Force unwrapping | Crashes | Use guard, if-let, nil coalescing |
| Main thread blocking | UI freezes | Use async/await, move work to background |
| Retain cycles | Memory leaks | Use [weak self] in closures |
| Ignoring errors | Silent failures | Always handle errors appropriately |
More from nhatmobile1/claude-skills
design-styles
Comprehensive web design aesthetics guide for applying specific design styles to projects. Use when: (1) User mentions a specific design style name (glassmorphism, brutalist, minimalist, neomorphism, cyberpunk, etc.), (2) User asks for design style recommendations or suggestions, (3) User wants to browse/see available design styles, (4) User is starting a frontend project and needs aesthetic guidance, (5) User asks what style fits their industry/audience/brand, (6) Working with the frontend-design skill and style direction is needed. Provides actionable design direction including color palettes, typography, layout principles, and implementation examples.
14color-palette
Create distinctive, accessible color palettes for UI/web design that avoid generic AI aesthetics. Use when designing websites, applications, or any digital interface requiring thoughtful color selection. Provides curated domain-specific palettes, color theory guidance, accessibility validation, and strategies to break away from overused patterns (purple gradients, orange-teal combinations, generic tech blues). Includes contrast checkers, palette generators, and comprehensive reference materials organized by domain (Tech/SaaS, E-commerce, Healthcare, Finance, Creative, Food).
4frontend-design-complete
Create distinctive, production-grade frontend interfaces with high design quality. Covers aesthetics, mobile-first patterns, modern CSS, performance (Core Web Vitals), dark mode/theming, AI-era patterns, interaction design, data visualization, fluid typography, responsive images, component architecture, forms, internationalization, cognitive accessibility, design tokens, and cascade layers. Use this as your single frontend design skill.
1