iOS Code Examples

Swift code examples and implementation patterns for our iOS development team

API Client Setup

Our robust API client implementation that handles authentication, token refresh, and error handling.

RentManagerAPI.swift

import Foundation
import Combine

class RentManagerAPI: ObservableObject {
    static let shared = RentManagerAPI()
    
    private let baseURL = "https://rentmanager.io/api"
    private let session = URLSession.shared
    
    @Published var isAuthenticated = false
    @Published var currentUser: User?
    
    private init() {
        // Check if user is already authenticated
        if TokenManager.shared.getAccessToken() != nil {
            isAuthenticated = true
            loadCurrentUser()
        }
    }
    
    // MARK: - Authentication
    
    func authenticateWithApple(identityToken: String, user: [String: Any]?) async throws -> AuthResponse {
        let endpoint = "/auth/apple"
        let body: [String: Any] = [
            "identityToken": identityToken,
            "user": user ?? [:]
        ]
        
        let response: AuthResponse = try await makeRequest(
            endpoint: endpoint,
            method: .POST,
            body: body,
            requiresAuth: false
        )
        
        if response.success {
            TokenManager.shared.saveTokens(
                accessToken: response.token,
                refreshToken: response.refreshToken
            )
            
            DispatchQueue.main.async {
                self.isAuthenticated = true
                self.currentUser = response.user
            }
        }
        
        return response
    }
    
    func logout() async {
        do {
            let _: LogoutResponse = try await makeRequest(
                endpoint: "/auth/logout",
                method: .GET
            )
        } catch {
            print("Logout request failed: \(error)")
        }
        
        TokenManager.shared.clearTokens()
        
        DispatchQueue.main.async {
            self.isAuthenticated = false
            self.currentUser = nil
        }
    }
    
    // MARK: - Properties
    
    func fetchProperties(page: Int = 1, limit: Int = 20) async throws -> PropertiesResponse {
        let endpoint = "/properties?page=\(page)&limit=\(limit)"
        return try await makeRequest(endpoint: endpoint, method: .GET)
    }
    
    func createProperty(_ property: PropertyRequest) async throws -> PropertyResponse {
        return try await makeRequest(
            endpoint: "/properties",
            method: .POST,
            body: property.dictionary
        )
    }
    
    func updateProperty(id: Int, property: PropertyRequest) async throws -> PropertyResponse {
        return try await makeRequest(
            endpoint: "/properties/\(id)",
            method: .PUT,
            body: property.dictionary
        )
    }
    
    func deleteProperty(id: Int) async throws {
        let _: EmptyResponse = try await makeRequest(
            endpoint: "/properties/\(id)",
            method: .DELETE
        )
    }
    
    // MARK: - Rentals
    
    func fetchRentals() async throws -> RentalsResponse {
        return try await makeRequest(endpoint: "/rentals", method: .GET)
    }
    
    func createAgreement(_ agreement: AgreementRequest) async throws -> AgreementResponse {
        return try await makeRequest(
            endpoint: "/agreements",
            method: .POST,
            body: agreement.dictionary
        )
    }
    
    // MARK: - Finances
    
    func fetchBalance() async throws -> BalanceResponse {
        return try await makeRequest(endpoint: "/finances/balance", method: .GET)
    }
    
    func fetchTransactions(page: Int = 1) async throws -> TransactionsResponse {
        let endpoint = "/finances/transactions?page=\(page)"
        return try await makeRequest(endpoint: endpoint, method: .GET)
    }
    
    // MARK: - Private Methods
    
    private func makeRequest(
        endpoint: String,
        method: HTTPMethod,
        body: [String: Any]? = nil,
        requiresAuth: Bool = true
    ) async throws -> T {
        guard let url = URL(string: baseURL + endpoint) else {
            throw APIError.invalidURL
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        if requiresAuth, let token = TokenManager.shared.getAccessToken() {
            request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }
        
        if let body = body {
            request.httpBody = try JSONSerialization.data(withJSONObject: body)
        }
        
        do {
            let (data, response) = try await session.data(for: request)
            
            if let httpResponse = response as? HTTPURLResponse {
                switch httpResponse.statusCode {
                case 200...299:
                    break
                case 401:
                    if requiresAuth {
                        try await refreshTokenAndRetry()
                        return try await makeRequest(
                            endpoint: endpoint,
                            method: method,
                            body: body,
                            requiresAuth: requiresAuth
                        )
                    }
                    throw APIError.unauthorized
                case 400...499:
                    throw APIError.clientError(httpResponse.statusCode)
                case 500...599:
                    throw APIError.serverError(httpResponse.statusCode)
                default:
                    throw APIError.unknown
                }
            }
            
            return try JSONDecoder().decode(T.self, from: data)
            
        } catch let error as APIError {
            throw error
        } catch {
            throw APIError.networkError(error)
        }
    }
    
    private func refreshTokenAndRetry() async throws {
        guard let refreshToken = TokenManager.shared.getRefreshToken() else {
            throw APIError.noRefreshToken
        }
        
        let refreshResponse: RefreshResponse = try await makeRequest(
            endpoint: "/auth/refresh",
            method: .POST,
            body: ["refreshToken": refreshToken],
            requiresAuth: false
        )
        
        TokenManager.shared.saveTokens(
            accessToken: refreshResponse.token,
            refreshToken: refreshToken
        )
    }
    
    private func loadCurrentUser() {
        Task {
            do {
                let user: User = try await makeRequest(endpoint: "/users/profile", method: .GET)
                DispatchQueue.main.async {
                    self.currentUser = user
                }
            } catch {
                print("Failed to load current user: \(error)")
            }
        }
    }
}

// MARK: - HTTP Method Enum

enum HTTPMethod: String {
    case GET = "GET"
    case POST = "POST"
    case PUT = "PUT"
    case DELETE = "DELETE"
    case PATCH = "PATCH"
}

// MARK: - API Error Enum

enum APIError: LocalizedError {
    case invalidURL
    case unauthorized
    case noRefreshToken
    case clientError(Int)
    case serverError(Int)
    case networkError(Error)
    case unknown
    
    var errorDescription: String? {
        switch self {
        case .invalidURL:
            return "Invalid URL"
        case .unauthorized:
            return "Unauthorized access"
        case .noRefreshToken:
            return "No refresh token available"
        case .clientError(let code):
            return "Client error: \(code)"
        case .serverError(let code):
            return "Server error: \(code)"
        case .networkError(let error):
            return "Network error: \(error.localizedDescription)"
        case .unknown:
            return "Unknown error occurred"
        }
    }
}

Property Management

Our property management implementation using MVVM pattern for the iOS application.

PropertyViewModel.swift

import Foundation
import Combine

@MainActor
class PropertyViewModel: ObservableObject {
    @Published var properties: [Property] = []
    @Published var isLoading = false
    @Published var errorMessage: String?
    @Published var showingCreateProperty = false
    
    private let api = RentManagerAPI.shared
    private var cancellables = Set()
    
    init() {
        loadProperties()
    }
    
    func loadProperties() {
        isLoading = true
        errorMessage = nil
        
        Task {
            do {
                let response = try await api.fetchProperties()
                self.properties = response.properties
            } catch {
                self.errorMessage = error.localizedDescription
            }
            self.isLoading = false
        }
    }
    
    func createProperty(_ propertyRequest: PropertyRequest) {
        Task {
            do {
                let response = try await api.createProperty(propertyRequest)
                self.properties.insert(response.property, at: 0)
                self.showingCreateProperty = false
            } catch {
                self.errorMessage = error.localizedDescription
            }
        }
    }
    
    func updateProperty(_ property: Property, with updates: PropertyRequest) {
        Task {
            do {
                let response = try await api.updateProperty(id: property.id, property: updates)
                if let index = properties.firstIndex(where: { $0.id == property.id }) {
                    properties[index] = response.property
                }
            } catch {
                self.errorMessage = error.localizedDescription
            }
        }
    }
    
    func deleteProperty(_ property: Property) {
        Task {
            do {
                try await api.deleteProperty(id: property.id)
                self.properties.removeAll { $0.id == property.id }
            } catch {
                self.errorMessage = error.localizedDescription
            }
        }
    }
    
    func toggleAvailability(for property: Property) {
        let updates = PropertyRequest(
            type: property.type,
            country: property.country,
            address: property.address,
            bedrooms: property.bedrooms,
            bathrooms: property.bathrooms,
            area: property.area,
            rentPrice: property.rentPrice,
            isAvailable: !property.isAvailable
        )
        
        updateProperty(property, with: updates)
    }
}

PropertyListView.swift

import SwiftUI

struct PropertyListView: View {
    @StateObject private var viewModel = PropertyViewModel()
    @State private var showingCreateProperty = false
    
    var body: some View {
        NavigationView {
            ZStack {
                if viewModel.isLoading {
                    ProgressView("Loading properties...")
                } else if viewModel.properties.isEmpty {
                    EmptyStateView(
                        icon: "house",
                        title: "No Properties",
                        message: "Add your first property to get started",
                        buttonTitle: "Add Property"
                    ) {
                        showingCreateProperty = true
                    }
                } else {
                    List {
                        ForEach(viewModel.properties) { property in
                            PropertyRowView(property: property) { action in
                                handlePropertyAction(action, for: property)
                            }
                        }
                        .onDelete(perform: deleteProperties)
                    }
                    .refreshable {
                        viewModel.loadProperties()
                    }
                }
            }
            .navigationTitle("Properties")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("Add") {
                        showingCreateProperty = true
                    }
                }
            }
            .sheet(isPresented: $showingCreateProperty) {
                CreatePropertyView { propertyRequest in
                    viewModel.createProperty(propertyRequest)
                }
            }
            .alert("Error", isPresented: .constant(viewModel.errorMessage != nil)) {
                Button("OK") {
                    viewModel.errorMessage = nil
                }
            } message: {
                Text(viewModel.errorMessage ?? "")
            }
        }
    }
    
    private func deleteProperties(offsets: IndexSet) {
        for index in offsets {
            let property = viewModel.properties[index]
            viewModel.deleteProperty(property)
        }
    }
    
    private func handlePropertyAction(_ action: PropertyAction, for property: Property) {
        switch action {
        case .toggleAvailability:
            viewModel.toggleAvailability(for: property)
        case .edit:
            // Handle edit action
            break
        case .viewDetails:
            // Handle view details action
            break
        }
    }
}

enum PropertyAction {
    case toggleAvailability
    case edit
    case viewDetails
}

PropertyRowView.swift

import SwiftUI

struct PropertyRowView: View {
    let property: Property
    let onAction: (PropertyAction) -> Void
    
    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack {
                VStack(alignment: .leading, spacing: 4) {
                    Text(property.address)
                        .font(.headline)
                        .lineLimit(2)
                    
                    if let type = property.type {
                        Text(type.capitalized)
                            .font(.caption)
                            .foregroundColor(.secondary)
                    }
                }
                
                Spacer()
                
                VStack(alignment: .trailing, spacing: 4) {
                    if let rentPrice = property.rentPrice {
                        Text("$\(rentPrice, specifier: "%.0f")")
                            .font(.title2)
                            .fontWeight(.bold)
                            .foregroundColor(.primary)
                    }
                    
                    StatusBadge(isAvailable: property.isAvailable)
                }
            }
            
            HStack {
                if let bedrooms = property.bedrooms, bedrooms > 0 {
                    PropertyFeature(icon: "bed.double", text: "\(bedrooms) bed")
                }
                
                if let bathrooms = property.bathrooms, bathrooms > 0 {
                    PropertyFeature(icon: "bathtub", text: "\(bathrooms) bath")
                }
                
                if let area = property.area, area > 0 {
                    PropertyFeature(
                        icon: "ruler",
                        text: "\(Int(area)) \(property.areaUnit ?? "m²")"
                    )
                }
                
                Spacer()
                
                Menu {
                    Button("Toggle Availability") {
                        onAction(.toggleAvailability)
                    }
                    
                    Button("Edit") {
                        onAction(.edit)
                    }
                    
                    Button("View Details") {
                        onAction(.viewDetails)
                    }
                } label: {
                    Image(systemName: "ellipsis")
                        .foregroundColor(.secondary)
                }
            }
        }
        .padding(.vertical, 4)
    }
}

struct PropertyFeature: View {
    let icon: String
    let text: String
    
    var body: some View {
        HStack(spacing: 4) {
            Image(systemName: icon)
                .font(.caption)
                .foregroundColor(.secondary)
            Text(text)
                .font(.caption)
                .foregroundColor(.secondary)
        }
    }
}

struct StatusBadge: View {
    let isAvailable: Bool
    
    var body: some View {
        Text(isAvailable ? "Available" : "Occupied")
            .font(.caption)
            .fontWeight(.medium)
            .padding(.horizontal, 8)
            .padding(.vertical, 4)
            .background(isAvailable ? Color.green.opacity(0.2) : Color.red.opacity(0.2))
            .foregroundColor(isAvailable ? .green : .red)
            .cornerRadius(12)
    }
}

SwiftUI Views

SwiftUI views implementation for our property creation and management features.

CreatePropertyView.swift

import SwiftUI

struct CreatePropertyView: View {
    @Environment(\.presentationMode) var presentationMode
    
    let onSave: (PropertyRequest) -> Void
    
    @State private var type = ""
    @State private var country = ""
    @State private var address = ""
    @State private var bedrooms = 1
    @State private var bathrooms = 1
    @State private var area: Double = 0
    @State private var areaUnit = "m2"
    @State private var rentPrice: Double = 0
    @State private var rentCurrency = "USD"
    @State private var isAvailable = true
    @State private var furnished = "no"
    
    private let propertyTypes = ["apartment", "house", "office", "studio", "villa"]
    private let areaUnits = ["m2", "ft2"]
    private let currencies = ["USD", "EUR", "GBP"]
    private let furnishedOptions = ["yes", "no", "partial"]
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Basic Information")) {
                    Picker("Property Type", selection: $type) {
                        Text("Select Type").tag("")
                        ForEach(propertyTypes, id: \.self) { type in
                            Text(type.capitalized).tag(type)
                        }
                    }
                    
                    TextField("Country Code (e.g., US)", text: $country)
                        .textInputAutocapitalization(.characters)
                        .autocorrectionDisabled()
                    
                    TextField("Full Address", text: $address, axis: .vertical)
                        .lineLimit(2...4)
                }
                
                Section(header: Text("Property Details")) {
                    Stepper("Bedrooms: \(bedrooms)", value: $bedrooms, in: 0...10)
                    Stepper("Bathrooms: \(bathrooms)", value: $bathrooms, in: 0...10)
                    
                    HStack {
                        TextField("Area", value: $area, format: .number)
                            .keyboardType(.decimalPad)
                        
                        Picker("Unit", selection: $areaUnit) {
                            ForEach(areaUnits, id: \.self) { unit in
                                Text(unit).tag(unit)
                            }
                        }
                        .pickerStyle(.segmented)
                        .frame(width: 100)
                    }
                    
                    Picker("Furnished", selection: $furnished) {
                        ForEach(furnishedOptions, id: \.self) { option in
                            Text(option.capitalized).tag(option)
                        }
                    }
                }
                
                Section(header: Text("Rental Information")) {
                    HStack {
                        TextField("Rent Price", value: $rentPrice, format: .number)
                            .keyboardType(.decimalPad)
                        
                        Picker("Currency", selection: $rentCurrency) {
                            ForEach(currencies, id: \.self) { currency in
                                Text(currency).tag(currency)
                            }
                        }
                        .pickerStyle(.menu)
                        .frame(width: 80)
                    }
                    
                    Toggle("Available for Rent", isOn: $isAvailable)
                }
            }
            .navigationTitle("New Property")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button("Cancel") {
                        presentationMode.wrappedValue.dismiss()
                    }
                }
                
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("Save") {
                        saveProperty()
                    }
                    .disabled(!isValidProperty)
                }
            }
        }
    }
    
    private var isValidProperty: Bool {
        !country.isEmpty && !address.isEmpty
    }
    
    private func saveProperty() {
        let propertyRequest = PropertyRequest(
            type: type.isEmpty ? nil : type,
            country: country,
            address: address,
            bedrooms: bedrooms,
            bathrooms: bathrooms,
            area: area > 0 ? area : nil,
            areaUnit: areaUnit,
            rentPrice: rentPrice > 0 ? rentPrice : nil,
            rentCurrency: rentCurrency,
            isAvailable: isAvailable,
            furnished: furnished
        )
        
        onSave(propertyRequest)
        presentationMode.wrappedValue.dismiss()
    }
}

EmptyStateView.swift

import SwiftUI

struct EmptyStateView: View {
    let icon: String
    let title: String
    let message: String
    let buttonTitle: String?
    let action: (() -> Void)?
    
    init(
        icon: String,
        title: String,
        message: String,
        buttonTitle: String? = nil,
        action: (() -> Void)? = nil
    ) {
        self.icon = icon
        self.title = title
        self.message = message
        self.buttonTitle = buttonTitle
        self.action = action
    }
    
    var body: some View {
        VStack(spacing: 24) {
            Image(systemName: icon)
                .font(.system(size: 64))
                .foregroundColor(.gray.opacity(0.5))
            
            VStack(spacing: 8) {
                Text(title)
                    .font(.title2)
                    .fontWeight(.semibold)
                    .foregroundColor(.primary)
                
                Text(message)
                    .font(.body)
                    .foregroundColor(.secondary)
                    .multilineTextAlignment(.center)
                    .padding(.horizontal)
            }
            
            if let buttonTitle = buttonTitle, let action = action {
                Button(action: action) {
                    Text(buttonTitle)
                        .font(.headline)
                        .foregroundColor(.white)
                        .padding(.horizontal, 24)
                        .padding(.vertical, 12)
                        .background(Color.blue)
                        .cornerRadius(8)
                }
            }
        }
        .padding()
    }
}

Data Models

Data models for our API integration with proper Codable implementation.

Property Models

import Foundation

// MARK: - Property Model
struct Property: Codable, Identifiable {
    let id: Int
    let type: String?
    let country: String
    let address: String
    let bedrooms: Int?
    let bathrooms: Int?
    let area: Double?
    let areaUnit: String?
    let rentPrice: Double?
    let rentCurrency: String?
    let isAvailable: Bool
    let furnished: String?
    let createdAt: Date
    let updatedAt: Date
    
    enum CodingKeys: String, CodingKey {
        case id, type, country, address, bedrooms, bathrooms, area
        case areaUnit = "area_unit"
        case rentPrice = "rent_price"
        case rentCurrency = "rent_currency"
        case isAvailable = "is_available"
        case furnished
        case createdAt = "created_at"
        case updatedAt = "updated_at"
    }
}

// MARK: - Property Request Model
struct PropertyRequest: Codable {
    let type: String?
    let country: String
    let address: String
    let bedrooms: Int?
    let bathrooms: Int?
    let area: Double?
    let areaUnit: String?
    let rentPrice: Double?
    let rentCurrency: String?
    let isAvailable: Bool
    let furnished: String?
    
    enum CodingKeys: String, CodingKey {
        case type, country, address, bedrooms, bathrooms, area
        case areaUnit = "area_unit"
        case rentPrice = "rent_price"
        case rentCurrency = "rent_currency"
        case isAvailable = "is_available"
        case furnished
    }
    
    var dictionary: [String: Any] {
        var dict: [String: Any] = [
            "country": country,
            "address": address,
            "is_available": isAvailable
        ]
        
        if let type = type { dict["type"] = type }
        if let bedrooms = bedrooms { dict["bedrooms"] = bedrooms }
        if let bathrooms = bathrooms { dict["bathrooms"] = bathrooms }
        if let area = area { dict["area"] = area }
        if let areaUnit = areaUnit { dict["area_unit"] = areaUnit }
        if let rentPrice = rentPrice { dict["rent_price"] = rentPrice }
        if let rentCurrency = rentCurrency { dict["rent_currency"] = rentCurrency }
        if let furnished = furnished { dict["furnished"] = furnished }
        
        return dict
    }
}

// MARK: - API Response Models
struct PropertiesResponse: Codable {
    let properties: [Property]
    let pagination: PaginationInfo
}

struct PropertyResponse: Codable {
    let id: Int
    let message: String
    let property: Property
}

struct PaginationInfo: Codable {
    let currentPage: Int
    let totalPages: Int
    let totalItems: Int
    let itemsPerPage: Int
    
    enum CodingKeys: String, CodingKey {
        case currentPage = "current_page"
        case totalPages = "total_pages"
        case totalItems = "total_items"
        case itemsPerPage = "items_per_page"
    }
}

User & Authentication Models

// MARK: - User Model
struct User: Codable, Identifiable {
    let id: Int
    let email: String
    let fullName: String
    let role: String
    let accessLevel: String
    let createdAt: Date
    
    enum CodingKeys: String, CodingKey {
        case id, email, role
        case fullName = "full_name"
        case accessLevel = "access_level"
        case createdAt = "created_at"
    }
}

// MARK: - Authentication Models
struct AuthResponse: Codable {
    let success: Bool
    let token: String
    let refreshToken: String
    let isNewUser: Bool
    let user: User
    
    enum CodingKeys: String, CodingKey {
        case success, token, user
        case refreshToken = "refreshToken"
        case isNewUser = "isNewUser"
    }
}

struct RefreshResponse: Codable {
    let token: String
}

struct LogoutResponse: Codable {
    let message: String
}

// MARK: - Financial Models
struct Balance: Codable {
    let amount: Double
    let currency: String
    let lastUpdated: Date
    
    enum CodingKeys: String, CodingKey {
        case amount, currency
        case lastUpdated = "last_updated"
    }
}

struct BalanceResponse: Codable {
    let balance: Balance
}

struct Transaction: Codable, Identifiable {
    let id: Int
    let type: String
    let amount: Double
    let currency: String
    let description: String
    let createdAt: Date
    
    enum CodingKeys: String, CodingKey {
        case id, type, amount, currency, description
        case createdAt = "created_at"
    }
}

struct TransactionsResponse: Codable {
    let transactions: [Transaction]
    let pagination: PaginationInfo
}

// MARK: - Rental Models
struct Rental: Codable, Identifiable {
    let id: Int
    let propertyId: Int
    let tenantName: String
    let startDate: Date
    let endDate: Date?
    let monthlyRent: Double
    let status: String
    
    enum CodingKeys: String, CodingKey {
        case id, status
        case propertyId = "property_id"
        case tenantName = "tenant_name"
        case startDate = "start_date"
        case endDate = "end_date"
        case monthlyRent = "monthly_rent"
    }
}

struct RentalsResponse: Codable {
    let rentals: [Rental]
}

// MARK: - Agreement Models
struct AgreementRequest: Codable {
    let propertyId: Int
    let tenantFullName: String
    let tenantPassportNumber: String
    let tenantCountry: String
    let startDate: String
    let endDate: String
    let monthlyRent: Double
    
    enum CodingKeys: String, CodingKey {
        case propertyId = "property_id"
        case tenantFullName = "tenant_full_name"
        case tenantPassportNumber = "tenant_passport_number"
        case tenantCountry = "tenant_country"
        case startDate = "start_date"
        case endDate = "end_date"
        case monthlyRent = "monthly_rent"
    }
    
    var dictionary: [String: Any] {
        return [
            "property_id": propertyId,
            "tenant_full_name": tenantFullName,
            "tenant_passport_number": tenantPassportNumber,
            "tenant_country": tenantCountry,
            "start_date": startDate,
            "end_date": endDate,
            "monthly_rent": monthlyRent
        ]
    }
}

struct Agreement: Codable, Identifiable {
    let id: Int
    let propertyId: Int
    let tenantFullName: String
    let startDate: Date
    let endDate: Date
    let monthlyRent: Double
    let status: String
    
    enum CodingKeys: String, CodingKey {
        case id, status
        case propertyId = "property_id"
        case tenantFullName = "tenant_full_name"
        case startDate = "start_date"
        case endDate = "end_date"
        case monthlyRent = "monthly_rent"
    }
}

struct AgreementResponse: Codable {
    let id: Int
    let message: String
    let agreement: Agreement
}

// MARK: - Empty Response
struct EmptyResponse: Codable {
    // Used for endpoints that don't return data
}

Error Handling

Error handling implementation for our iOS application development.

ErrorManager.swift

import Foundation
import SwiftUI

class ErrorManager: ObservableObject {
    @Published var currentError: ErrorInfo?
    @Published var showingError = false
    
    func handle(_ error: Error) {
        DispatchQueue.main.async {
            self.currentError = ErrorInfo(from: error)
            self.showingError = true
        }
    }
    
    func clearError() {
        currentError = nil
        showingError = false
    }
}

struct ErrorInfo {
    let title: String
    let message: String
    let isRetryable: Bool
    
    init(from error: Error) {
        switch error {
        case let apiError as APIError:
            self.init(from: apiError)
        case let urlError as URLError:
            self.init(from: urlError)
        default:
            title = "Unknown Error"
            message = error.localizedDescription
            isRetryable = true
        }
    }
    
    private init(from apiError: APIError) {
        switch apiError {
        case .invalidURL:
            title = "Invalid URL"
            message = "The request URL is invalid. Please try again."
            isRetryable = false
            
        case .unauthorized:
            title = "Authentication Required"
            message = "Please sign in again to continue."
            isRetryable = false
            
        case .noRefreshToken:
            title = "Session Expired"
            message = "Your session has expired. Please sign in again."
            isRetryable = false
            
        case .clientError(let code):
            title = "Request Error"
            message = getClientErrorMessage(for: code)
            isRetryable = code != 404
            
        case .serverError(let code):
            title = "Server Error"
            message = "The server is experiencing issues (\(code)). Please try again later."
            isRetryable = true
            
        case .networkError(let underlyingError):
            title = "Network Error"
            message = "Please check your internet connection and try again."
            isRetryable = true
            
        case .unknown:
            title = "Unknown Error"
            message = "An unexpected error occurred. Please try again."
            isRetryable = true
        }
    }
    
    private init(from urlError: URLError) {
        title = "Connection Error"
        
        switch urlError.code {
        case .notConnectedToInternet:
            message = "No internet connection available."
        case .timedOut:
            message = "The request timed out. Please try again."
        case .cannotConnectToHost:
            message = "Cannot connect to the server. Please try again later."
        default:
            message = "A network error occurred. Please check your connection."
        }
        
        isRetryable = true
    }
    
    private func getClientErrorMessage(for code: Int) -> String {
        switch code {
        case 400:
            return "The request was invalid. Please check your input."
        case 401:
            return "Authentication failed. Please sign in again."
        case 403:
            return "You don't have permission to perform this action."
        case 404:
            return "The requested resource was not found."
        case 409:
            return "This action conflicts with existing data."
        case 422:
            return "The provided data is invalid."
        default:
            return "A client error occurred (\(code))."
        }
    }
}

// MARK: - Error Alert View
struct ErrorAlert: ViewModifier {
    @ObservedObject var errorManager: ErrorManager
    let retryAction: (() -> Void)?
    
    func body(content: Content) -> some View {
        content
            .alert("Error", isPresented: $errorManager.showingError) {
                Button("OK") {
                    errorManager.clearError()
                }
                
                if let error = errorManager.currentError,
                   error.isRetryable,
                   let retryAction = retryAction {
                    Button("Retry") {
                        errorManager.clearError()
                        retryAction()
                    }
                }
            } message: {
                if let error = errorManager.currentError {
                    Text(error.message)
                }
            }
    }
}

extension View {
    func errorAlert(
        errorManager: ErrorManager,
        retryAction: (() -> Void)? = nil
    ) -> some View {
        modifier(ErrorAlert(errorManager: errorManager, retryAction: retryAction))
    }
}

Modern Async/Await Examples

Swift concurrency implementation using async/await patterns in our codebase.

AsyncPropertyService.swift

import Foundation

actor PropertyService {
    private let api = RentManagerAPI.shared
    private var cachedProperties: [Property] = []
    private var lastFetchTime: Date?
    
    func getProperties(forceRefresh: Bool = false) async throws -> [Property] {
        if !forceRefresh,
           let lastFetch = lastFetchTime,
           Date().timeIntervalSince(lastFetch) < 300, // 5 minutes cache
           !cachedProperties.isEmpty {
            return cachedProperties
        }
        
        let response = try await api.fetchProperties()
        cachedProperties = response.properties
        lastFetchTime = Date()
        
        return cachedProperties
    }
    
    func createProperty(_ request: PropertyRequest) async throws -> Property {
        let response = try await api.createProperty(request)
        cachedProperties.insert(response.property, at: 0)
        return response.property
    }
    
    func updateProperty(id: Int, with request: PropertyRequest) async throws -> Property {
        let response = try await api.updateProperty(id: id, property: request)
        
        if let index = cachedProperties.firstIndex(where: { $0.id == id }) {
            cachedProperties[index] = response.property
        }
        
        return response.property
    }
    
    func deleteProperty(id: Int) async throws {
        try await api.deleteProperty(id: id)
        cachedProperties.removeAll { $0.id == id }
    }
}

// MARK: - Usage Example in ViewModel
@MainActor
class ModernPropertyViewModel: ObservableObject {
    @Published var properties: [Property] = []
    @Published var isLoading = false
    @Published var error: Error?
    
    private let propertyService = PropertyService()
    
    func loadProperties(forceRefresh: Bool = false) async {
        isLoading = true
        error = nil
        
        do {
            properties = try await propertyService.getProperties(forceRefresh: forceRefresh)
        } catch {
            self.error = error
        }
        
        isLoading = false
    }
    
    func createProperty(_ request: PropertyRequest) async {
        do {
            let newProperty = try await propertyService.createProperty(request)
            properties.insert(newProperty, at: 0)
        } catch {
            self.error = error
        }
    }
    
    func updateProperty(id: Int, with request: PropertyRequest) async {
        do {
            let updatedProperty = try await propertyService.updateProperty(id: id, with: request)
            if let index = properties.firstIndex(where: { $0.id == id }) {
                properties[index] = updatedProperty
            }
        } catch {
            self.error = error
        }
    }
    
    func deleteProperty(id: Int) async {
        do {
            try await propertyService.deleteProperty(id: id)
            properties.removeAll { $0.id == id }
        } catch {
            self.error = error
        }
    }
}

SwiftUI with Async/Await

struct ModernPropertyListView: View {
    @StateObject private var viewModel = ModernPropertyViewModel()
    @StateObject private var errorManager = ErrorManager()
    
    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.properties) { property in
                    PropertyRowView(property: property) { action in
                        Task {
                            await handlePropertyAction(action, for: property)
                        }
                    }
                }
                .onDelete { indexSet in
                    Task {
                        await deleteProperties(at: indexSet)
                    }
                }
            }
            .navigationTitle("Properties")
            .refreshable {
                await viewModel.loadProperties(forceRefresh: true)
            }
            .task {
                await viewModel.loadProperties()
            }
            .onChange(of: viewModel.error) { error in
                if let error = error {
                    errorManager.handle(error)
                }
            }
            .errorAlert(errorManager: errorManager) {
                Task {
                    await viewModel.loadProperties(forceRefresh: true)
                }
            }
        }
    }
    
    private func handlePropertyAction(_ action: PropertyAction, for property: Property) async {
        switch action {
        case .toggleAvailability:
            let request = PropertyRequest(
                type: property.type,
                country: property.country,
                address: property.address,
                bedrooms: property.bedrooms,
                bathrooms: property.bathrooms,
                area: property.area,
                areaUnit: property.areaUnit,
                rentPrice: property.rentPrice,
                rentCurrency: property.rentCurrency,
                isAvailable: !property.isAvailable,
                furnished: property.furnished
            )
            await viewModel.updateProperty(id: property.id, with: request)
            
        case .edit:
            // Handle edit
            break
            
        case .viewDetails:
            // Handle view details
            break
        }
    }
    
    private func deleteProperties(at indexSet: IndexSet) async {
        for index in indexSet {
            let property = viewModel.properties[index]
            await viewModel.deleteProperty(id: property.id)
        }
    }
}