import SwiftUI
struct DetailRow: View {
let icon: String
let title: String
let value: String
var body: some View {
HStack {
Image(systemName: icon)
.foregroundColor(.blue)
.frame(width: 24)
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.caption)
.foregroundColor(.secondary)
Text(value)
.font(.body)
}
}
}
}
Presentation Layer
Feature/Presentation/CloudUserViewModel.swift
import Combine
import Foundation
class CloudUserViewModel: ObservableObject {
@Published var users: [UserEntity] = []
@Published var isLoading = false
@Published var errorMessage: String?
@Published var searchText = ""
private let getUsersUseCase = CloudUserUseCaseImpl()
private let syncUseCase = CloudSyncUseCaseImpl()
var filteredUsers: [UserEntity] {
if searchText.isEmpty {
return users
}
return users.filter { user in
user.name.localizedCaseInsensitiveContains(searchText) ||
user.username.localizedCaseInsensitiveContains(searchText) ||
user.email.localizedCaseInsensitiveContains(searchText) ||
user.company.localizedCaseInsensitiveContains(searchText)
}
}
func loadUsers() async {
isLoading = true
errorMessage = nil
do {
users = try await getUsersUseCase.execute()
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
func syncUsers() async {
isLoading = true
errorMessage = nil
do {
try await syncUseCase.execute()
users = try await getUsersUseCase.execute()
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
}
Domain Layer
Feature/Domain/Entities/UserEntity.swift
struct UserEntity: Identifiable {
let id: Int
let name: String
let username: String
let email: String
let city: String
let company: String
let phone: String
let website: String
let catchPhrase: String
}
struct CloudUserRequest: Codable {
var endpoint: String = "/users"
}
Feature/Data/Network/CloudUserResponse.swift
struct CloudUserResponse: Codable {
let id: Int
let name: String
let username: String
let email: String
let address: Address
let phone: String
let website: String
let company: Company
struct Address: Codable {
let street: String
let suite: String
let city: String
let zipcode: String
let geo: Geo
struct Geo: Codable {
let lat: String
let lng: String
}
}
struct Company: Codable {
let name: String
let catchPhrase: String
let bs: String
}
}
Feature/Data/Network/CloudUserApi.swift
import Foundation
protocol CloudUserApi {
func fetchUsers() async throws -> [CloudUserResponse]
}
class CloudUserApiImpl: CloudUserApi {
private let baseURL = "https://jsonplaceholder.typicode.com"
func fetchUsers() async throws -> [CloudUserResponse] {
guard let url = URL(string: "\(baseURL)/users") else {
throw URLError(.badURL)
}
let (data, _) = try await URLSession.shared.data(from: url)
let users = try JSONDecoder().decode([CloudUserResponse].self, from: data)
return users
}
}