← Back to Guides

Swift & SwiftUI iOS Development Guide

📖 18 min read | 📅 January 2025 | 🏷️ Mobile Development

Introduction

Swift and SwiftUI are Apple's modern tools for building iOS, iPadOS, macOS, watchOS, and tvOS applications. SwiftUI's declarative syntax makes it easy to build beautiful, responsive interfaces with less code.

1. Swift Fundamentals

// Variables and Constants
var mutableValue = 10
let constantValue = 20

// Optionals
var name: String? = "John"
if let unwrappedName = name {
    print("Hello, \(unwrappedName)")
}

// Nil coalescing
let displayName = name ?? "Guest"

// Guard statements
func greet(name: String?) {
    guard let name = name else { return }
    print("Hello, \(name)")
}

// Structures
struct User {
    var id: Int
    var name: String
    var email: String
    
    func isValid() -> Bool {
        return !email.isEmpty
    }
}

// Classes
class ViewModel: ObservableObject {
    @Published var count = 0
    
    func increment() {
        count += 1
    }
}

// Enums with associated values
enum Result<T, E: Error> {
    case success(T)
    case failure(E)
}

// Closures
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
let filtered = numbers.filter { $0 > 2 }

// Async/await
func fetchData() async throws -> String {
    let url = URL(string: "https://api.example.com/data")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return String(data: data, encoding: .utf8) ?? ""
}

2. SwiftUI Basics

import SwiftUI

struct ContentView: View {
    @State private var count = 0
    @State private var name = ""
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Counter: \(count)")
                .font(.largeTitle)
                .foregroundColor(.blue)
            
            HStack {
                Button("Decrement") {
                    count -= 1
                }
                .buttonStyle(.bordered)
                
                Button("Increment") {
                    count += 1
                }
                .buttonStyle(.borderedProminent)
            }
            
            TextField("Enter name", text: $name)
                .textFieldStyle(.roundedBorder)
                .padding()
            
            Image(systemName: "star.fill")
                .resizable()
                .frame(width: 50, height: 50)
                .foregroundColor(.yellow)
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

3. State Management

// @State for local state
struct CounterView: View {
    @State private var count = 0
    
    var body: some View {
        Button("Count: \(count)") {
            count += 1
        }
    }
}

// @Binding for two-way data flow
struct ChildView: View {
    @Binding var text: String
    
    var body: some View {
        TextField("Enter text", text: $text)
    }
}

struct ParentView: View {
    @State private var inputText = ""
    
    var body: some View {
        VStack {
            ChildView(text: $inputText)
            Text("You typed: \(inputText)")
        }
    }
}

// @StateObject and @ObservedObject
class DataViewModel: ObservableObject {
    @Published var items: [String] = []
    @Published var isLoading = false
    
    func loadData() {
        isLoading = true
        Task {
            try? await Task.sleep(for: .seconds(1))
            items = ["Item 1", "Item 2", "Item 3"]
            isLoading = false
        }
    }
}

struct DataView: View {
    @StateObject private var viewModel = DataViewModel()
    
    var body: some View {
        List(viewModel.items, id: \.self) { item in
            Text(item)
        }
        .onAppear {
            viewModel.loadData()
        }
    }
}

// @EnvironmentObject for dependency injection
class AppState: ObservableObject {
    @Published var isLoggedIn = false
    @Published var username = ""
}

@main
struct MyApp: App {
    @StateObject private var appState = AppState()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(appState)
        }
    }
}

struct SomeView: View {
    @EnvironmentObject var appState: AppState
    
    var body: some View {
        Text("User: \(appState.username)")
    }
}

4. Navigation

// NavigationStack (iOS 16+)
struct NavigationExample: View {
    var body: some View {
        NavigationStack {
            List(1...20, id: \.self) { item in
                NavigationLink("Item \(item)", value: item)
            }
            .navigationDestination(for: Int.self) { item in
                DetailView(itemNumber: item)
            }
            .navigationTitle("Items")
        }
    }
}

struct DetailView: View {
    let itemNumber: Int
    
    var body: some View {
        Text("Detail for item \(itemNumber)")
            .navigationTitle("Item \(itemNumber)")
    }
}

// Programmatic navigation
struct ProgrammaticNavigation: View {
    @State private var path = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $path) {
            Button("Go to Details") {
                path.append("details")
            }
            .navigationDestination(for: String.self) { value in
                DetailScreen(onNext: {
                    path.append("more")
                })
            }
        }
    }
}

// Sheets and full screen covers
struct SheetExample: View {
    @State private var showSheet = false
    @State private var showFullScreen = false
    
    var body: some View {
        VStack {
            Button("Show Sheet") {
                showSheet = true
            }
            .sheet(isPresented: $showSheet) {
                SheetContent()
            }
            
            Button("Show Full Screen") {
                showFullScreen = true
            }
            .fullScreenCover(isPresented: $showFullScreen) {
                FullScreenContent()
            }
        }
    }
}

5. Lists and Grids

// List
struct UserListView: View {
    let users = ["Alice", "Bob", "Charlie"]
    
    var body: some View {
        List(users, id: \.self) { user in
            HStack {
                Image(systemName: "person.circle")
                Text(user)
                Spacer()
                Image(systemName: "chevron.right")
            }
        }
    }
}

// LazyVStack for custom scrolling
struct LazyScrollView: View {
    var body: some View {
        ScrollView {
            LazyVStack(spacing: 10) {
                ForEach(0..<100) { index in
                    CardView(number: index)
                }
            }
        }
    }
}

// LazyVGrid for grid layout
struct GridView: View {
    let columns = [
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(0..<20) { index in
                    RoundedRectangle(cornerRadius: 10)
                        .fill(Color.blue)
                        .frame(height: 100)
                        .overlay(Text("\(index)"))
                }
            }
            .padding()
        }
    }
}

6. Networking

// API Service
struct User: Codable, Identifiable {
    let id: Int
    let name: String
    let email: String
}

class APIService {
    static let shared = APIService()
    
    func fetchUsers() async throws -> [User] {
        guard let url = URL(string: "https://api.example.com/users") else {
            throw URLError(.badURL)
        }
        
        let (data, response) = try await URLSession.shared.data(from: url)
        
        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        
        let users = try JSONDecoder().decode([User].self, from: data)
        return users
    }
    
    func createUser(_ user: User) async throws -> User {
        guard let url = URL(string: "https://api.example.com/users") else {
            throw URLError(.badURL)
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try JSONEncoder().encode(user)
        
        let (data, _) = try await URLSession.shared.data(for: request)
        return try JSONDecoder().decode(User.self, from: data)
    }
}

// ViewModel with async data
@MainActor
class UsersViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false
    @Published var error: String?
    
    func loadUsers() async {
        isLoading = true
        error = nil
        
        do {
            users = try await APIService.shared.fetchUsers()
        } catch {
            self.error = error.localizedDescription
        }
        
        isLoading = false
    }
}

// View with async data
struct UsersView: View {
    @StateObject private var viewModel = UsersViewModel()
    
    var body: some View {
        Group {
            if viewModel.isLoading {
                ProgressView()
            } else if let error = viewModel.error {
                Text("Error: \(error)")
            } else {
                List(viewModel.users) { user in
                    VStack(alignment: .leading) {
                        Text(user.name).font(.headline)
                        Text(user.email).font(.caption)
                    }
                }
            }
        }
        .task {
            await viewModel.loadUsers()
        }
    }
}

7. Core Data

import CoreData

// Create Core Data stack
class PersistenceController {
    static let shared = PersistenceController()
    
    let container: NSPersistentContainer
    
    init() {
        container = NSPersistentContainer(name: "Model")
        container.loadPersistentStores { description, error in
            if let error = error {
                fatalError("Core Data failed: \(error)")
            }
        }
    }
}

// Use in SwiftUI
@main
struct MyApp: App {
    let persistenceController = PersistenceController.shared
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, 
                    persistenceController.container.viewContext)
        }
    }
}

// Fetch data
struct ItemsView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)]
    ) private var items: FetchedResults<Item>
    
    var body: some View {
        List {
            ForEach(items) { item in
                Text(item.name ?? "")
            }
            .onDelete(perform: deleteItems)
        }
        .toolbar {
            Button("Add") {
                addItem()
            }
        }
    }
    
    private func addItem() {
        let newItem = Item(context: viewContext)
        newItem.timestamp = Date()
        newItem.name = "New Item"
        
        try? viewContext.save()
    }
    
    private func deleteItems(offsets: IndexSet) {
        offsets.map { items[$0] }.forEach(viewContext.delete)
        try? viewContext.save()
    }
}

8. Animations

struct AnimationExamples: View {
    @State private var scale: CGFloat = 1.0
    @State private var rotation: Double = 0
    @State private var offset: CGFloat = 0
    
    var body: some View {
        VStack(spacing: 30) {
            // Scale animation
            Circle()
                .fill(Color.blue)
                .frame(width: 100, height: 100)
                .scaleEffect(scale)
                .onTapGesture {
                    withAnimation(.spring(response: 0.5, dampingFraction: 0.5)) {
                        scale = scale == 1.0 ? 1.5 : 1.0
                    }
                }
            
            // Rotation animation
            Rectangle()
                .fill(Color.green)
                .frame(width: 100, height: 100)
                .rotationEffect(.degrees(rotation))
                .onTapGesture {
                    withAnimation(.linear(duration: 1)) {
                        rotation += 360
                    }
                }
            
            // Offset animation
            RoundedRectangle(cornerRadius: 10)
                .fill(Color.red)
                .frame(width: 100, height: 100)
                .offset(x: offset)
                .onTapGesture {
                    withAnimation(.easeInOut) {
                        offset = offset == 0 ? 100 : 0
                    }
                }
        }
    }
}

9. Custom Views and Modifiers

// Custom view
struct CardView<Content: View>: View {
    let content: Content
    
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    
    var body: some View {
        content
            .padding()
            .background(Color.white)
            .cornerRadius(10)
            .shadow(radius: 5)
    }
}

// Custom modifier
struct PrimaryButtonStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.headline)
            .foregroundColor(.white)
            .padding()
            .background(Color.blue)
            .cornerRadius(10)
    }
}

extension View {
    func primaryButtonStyle() -> some View {
        modifier(PrimaryButtonStyle())
    }
}

// Usage
Button("Click Me") {
    print("Tapped")
}
.primaryButtonStyle()

10. Testing

import XCTest
@testable import MyApp

class ViewModelTests: XCTestCase {
    var viewModel: DataViewModel!
    
    override func setUp() {
        super.setUp()
        viewModel = DataViewModel()
    }
    
    func testLoadData() async {
        await viewModel.loadData()
        XCTAssertFalse(viewModel.items.isEmpty)
        XCTAssertFalse(viewModel.isLoading)
    }
}

// UI Tests
class UITests: XCTestCase {
    func testButtonTap() {
        let app = XCUIApplication()
        app.launch()
        
        let button = app.buttons["Increment"]
        button.tap()
        
        let label = app.staticTexts["Counter: 1"]
        XCTAssertTrue(label.exists)
    }
}
💡 Best Practices:

Conclusion

Swift and SwiftUI provide a modern, efficient way to build iOS applications. Master these concepts to create beautiful, performant apps that follow Apple's design principles and best practices.