iOS Development

Introduction to iOS Development

iOS development involves creating applications for Apple's iOS platform using Swift or Objective-C. This guide focuses on modern iOS development using Swift and the latest tools and frameworks.

Development Requirements:

  • Mac computer with macOS
  • Xcode IDE
  • Apple Developer Account
  • iOS SDK
  • Swift programming language
  • iOS Simulator

Swift Programming Basics

Swift Language Features

// Variables and Constants
let constant = "This cannot be changed"
var variable = "This can be changed"

// Type Inference and Annotation
let inferredInt = 42
let explicitDouble: Double = 42.0

// Optionals
var optionalString: String?
var unwrappedString = optionalString ?? "default value"

// Guard Statement
func processAge(_ age: Int?) {
    guard let validAge = age, validAge >= 18 else {
        print("Invalid age")
        return
    }
    print("Processing age: \(validAge)")
}

// Structures
struct User {
    let id: String
    var name: String
    var email: String
    
    mutating func updateEmail(_ newEmail: String) {
        email = newEmail
    }
}

// Classes
class Account {
    let id: String
    var balance: Double
    
    init(id: String, balance: Double) {
        self.id = id
        self.balance = balance
    }
    
    func deposit(_ amount: Double) {
        balance += amount
    }
    
    func withdraw(_ amount: Double) throws {
        guard balance >= amount else {
            throw NSError(
                domain: "AccountError",
                code: 1,
                userInfo: [
                    NSLocalizedDescriptionKey:
                        "Insufficient funds"
                ]
            )
        }
        balance -= amount
    }
}

// Protocols
protocol DataProvider {
    func fetchData() async throws -> Data
}

// Extensions
extension String {
    var isValidEmail: Bool {
        let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let predicate = NSPredicate(format: "SELF MATCHES %@", pattern)
        return predicate.evaluate(with: self)
    }
}

// Enums with Associated Values
enum NetworkError: Error {
    case invalidURL
    case noData
    case serverError(Int)
    case decodingError(Error)
}

UI Development with SwiftUI

SwiftUI View Example

import SwiftUI

struct ContentView: View {
    @State private var username = ""
    @State private var password = ""
    @State private var isLoading = false
    @State private var showAlert = false
    @State private var alertMessage = ""
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Login")) {
                    TextField("Username", text: $username)
                        .autocapitalization(.none)
                    
                    SecureField("Password", text: $password)
                }
                
                Section {
                    Button(action: login) {
                        if isLoading {
                            ProgressView()
                        } else {
                            Text("Sign In")
                                .frame(maxWidth: .infinity)
                        }
                    }
                    .disabled(username.isEmpty || password.isEmpty)
                }
            }
            .navigationTitle("Welcome")
            .alert("Error", isPresented: $showAlert) {
                Button("OK", role: .cancel) {}
            } message: {
                Text(alertMessage)
            }
        }
    }
    
    private func login() {
        isLoading = true
        
        // Simulated network call
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            isLoading = false
            
            if username == "test" && password == "password" {
                // Success - Navigate to next screen
            } else {
                alertMessage = "Invalid credentials"
                showAlert = true
            }
        }
    }
}

// Preview
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Custom Components

struct CustomButton: View {
    let title: String
    let action: () -> Void
    
    var body: some View {
        Button(action: action) {
            Text(title)
                .font(.headline)
                .foregroundColor(.white)
                .frame(maxWidth: .infinity)
                .padding()
                .background(Color.blue)
                .cornerRadius(10)
        }
    }
}

struct LoadingView: View {
    let message: String
    
    var body: some View {
        VStack(spacing: 20) {
            ProgressView()
                .scaleEffect(1.5)
            
            Text(message)
                .font(.body)
                .foregroundColor(.secondary)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.black.opacity(0.1))
    }

App Architecture

MVVM Architecture Example

// Model
struct User: Codable {
    let id: String
    let name: String
    let email: String
}

// ViewModel
class UserViewModel: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false
    @Published var error: Error?
    
    private let userService: UserServiceProtocol
    
    init(userService: UserServiceProtocol) {
        self.userService = userService
    }
    
    @MainActor
    func fetchUser(id: String) async {
        isLoading = true
        error = nil
        
        do {
            user = try await userService.fetchUser(id: id)
        } catch {
            self.error = error
        }
        
        isLoading = false
    }
}

// Service Protocol
protocol UserServiceProtocol {
    func fetchUser(id: String) async throws -> User
}

// Service Implementation
class UserService: UserServiceProtocol {
    func fetchUser(id: String) async throws -> User {
        guard let url = URL(string: "https://api.example.com/users/\(id)") 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)
        }
        
        return try JSONDecoder().decode(User.self, from: data)
    }
}

Networking

Network Layer Implementation

// Network Error Enum
enum NetworkError: LocalizedError {
    case invalidURL
    case requestFailed(Error)
    case invalidResponse
    case decodingError(Error)
    
    var errorDescription: String? {
        switch self {
        case .invalidURL:
            return "Invalid URL"
        case .requestFailed(let error):
            return error.localizedDescription
        case .invalidResponse:
            return "Invalid server response"
        case .decodingError(let error):
            return "Decoding error: \(error.localizedDescription)"
        }
    }
}

// Network Service Protocol
protocol NetworkServiceProtocol {
    func fetch(
        _ endpoint: String,
        method: String,
        body: Data?
    ) async throws -> T
}

// Network Service Implementation
class NetworkService: NetworkServiceProtocol {
    private let baseURL: String
    private let session: URLSession
    
    init(baseURL: String, session: URLSession = .shared) {
        self.baseURL = baseURL
        self.session = session
    }
    
    func fetch(
        _ endpoint: String,
        method: String = "GET",
        body: Data? = nil
    ) async throws -> T {
        guard let url = URL(string: baseURL + endpoint) else {
            throw NetworkError.invalidURL
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = method
        request.httpBody = body
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        do {
            let (data, response) = try await session.data(for: request)
            
            guard let httpResponse = response as? HTTPURLResponse,
                  (200...299).contains(httpResponse.statusCode) else {
                throw NetworkError.invalidResponse
            }
            
            do {
                return try JSONDecoder().decode(T.self, from: data)
            } catch {
                throw NetworkError.decodingError(error)
            }
        } catch {
            throw NetworkError.requestFailed(error)
        }
    }
}

Data Persistence

CoreData Implementation

// CoreData Manager
class CoreDataManager {
    static let shared = CoreDataManager()
    
    private let persistentContainer: NSPersistentContainer
    
    private init() {
        persistentContainer = NSPersistentContainer(name: "DataModel")
        persistentContainer.loadPersistentStores { description, error in
            if let error = error {
                fatalError("Core Data store failed to load: \(error)")
            }
        }
    }
    
    var context: NSManagedObjectContext {
        persistentContainer.viewContext
    }
    
    func saveContext() {
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let error = error as NSError
                fatalError("An error occurred: \(error)")
            }
        }
    }
}

// Repository Pattern
protocol UserRepository {
    func save(_ user: User) throws
    func fetchUser(id: String) throws -> User?
    func fetchAllUsers() throws -> [User]
    func delete(_ user: User) throws
}

class CoreDataUserRepository: UserRepository {
    private let context: NSManagedObjectContext
    
    init(context: NSManagedObjectContext) {
        self.context = context
    }
    
    func save(_ user: User) throws {
        let userEntity = UserEntity(context: context)
        userEntity.id = user.id
        userEntity.name = user.name
        userEntity.email = user.email
        
        try context.save()
    }
    
    func fetchUser(id: String) throws -> User? {
        let request = UserEntity.fetchRequest()
        request.predicate = NSPredicate(
            format: "id == %@",
            id
        )
        
        let result = try context.fetch(request)
        return result.first.map { User(entity: $0) }
    }
    
    func fetchAllUsers() throws -> [User] {
        let request = UserEntity.fetchRequest()
        let results = try context.fetch(request)
        return results.map { User(entity: $0) }
    }
    
    func delete(_ user: User) throws {
        guard let userEntity = try fetchUserEntity(id: user.id) else {
            return
        }
        
        context.delete(userEntity)
        try context.save()
    }
    
    private func fetchUserEntity(id: String) throws -> UserEntity? {
        let request = UserEntity.fetchRequest()
        request.predicate = NSPredicate(
            format: "id == %@",
            id
        )
        
        return try context.fetch(request).first
    }
}

App Deployment

Deployment Steps:

  • Configure App Settings:
    • Bundle identifier
    • Version number
    • Build number
    • Deployment target
  • Code Signing:
    • Certificates
    • Provisioning profiles
    • Development team
  • App Store Connect:
    • App information
    • Screenshots
    • Description
    • Keywords
  • Testing:
    • TestFlight
    • Beta testing
    • Review guidelines