--- name: swift-enterprise-architect description: Kiến trúc và patterns cho ứng dụng SwiftUI Enterprise (MVVM, DI, Navigation, Project Structure). Use for iOS/macOS apps, SwiftUI development, hoặc khi cần structured Swift architecture. compatibility: "Swift 5.9+, iOS 17+, macOS 14+, SwiftUI" metadata: author: Velik Ho version: "1.0" references: "Apple SwiftUI Documentation, Swift Concurrency" --- # Swift Enterprise Development Workflow Quy trình 4 giai đoạn để phát triển ứng dụng SwiftUI theo chuẩn Enterprise. ## When to Use This Skill / Khi Nào Sử Dụng Use this skill when: - Building iOS/macOS/visionOS apps / Xây dựng app Apple platforms - Creating enterprise SwiftUI applications / Tạo ứng dụng SwiftUI enterprise - Need MVVM + DI architecture / Cần kiến trúc MVVM + DI - Implementing navigation patterns / Triển khai điều hướng **DO NOT use when:** - Simple single-screen apps / App đơn giản 1 màn hình - UIKit-only projects / Dự án chỉ UIKit - Backend Swift (use Vapor patterns) / Swift backend ## Overview / Tổng Quan ``` ┌──────────────────────────────────────────────────────────────────┐ │ WORKFLOW 4 GIAI ĐOẠN (Swift Enterprise) │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────────┐ │ │ │ PHASE 1 │────►│ PHASE 2 │ │ │ │ PROJECT │ │ ARCHITECTURE │ │ │ │ STRUCTURE │ │ │ │ │ │ - Folders │ │ - MVVM Pattern │ │ │ │ - Resources │ │ - DI Setup │ │ │ │ - Config │ │ - Services │ │ │ └─────────────┘ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────┐ ┌─────────────────┐ │ │ │ PHASE 4 │◄────│ PHASE 3 │ │ │ │ PLATFORM │ │ UI & NAV │ │ │ │ │ │ │ │ │ │ - Extensions│ │ - TabView │ │ │ │ - Platform │ │ - NavStack │ │ │ │ - Native │ │ - Sheets │ │ │ └─────────────┘ └─────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘ ``` --- ## Phase 1: Project Structure / Cấu Trúc Dự Án **Goal**: Thiết lập cấu trúc thư mục chuẩn Enterprise ### Project Structure ``` MyApp/ ├── MyAppApp.swift # App entry point ├── ContentView.swift # Root view ├── Core/ # Core utilities │ ├── Constants/ │ │ └── Constants.swift # App-wide constants │ └── Extensions/ │ ├── String+Extensions.swift │ └── View+Extensions.swift ├── Models/ # Data models │ └── User.swift ├── Services/ # Business services │ ├── APIService.swift │ └── AuthManager.swift ├── ViewModels/ # MVVM ViewModels │ ├── AuthViewModel.swift │ └── HomeViewModel.swift ├── Views/ # SwiftUI Views │ ├── Auth/ │ │ ├── LoginView.swift │ │ └── RegisterView.swift │ ├── Home/ │ │ └── HomeView.swift │ └── Screens/ │ ├── ProfileView.swift │ └── SettingsView.swift └── Resources/ ├── Assets.xcassets/ └── Localizable.strings ``` ### Constants Pattern ```swift // Core/Constants/Constants.swift // MARK: - API Configuration enum APIConfig { static let baseURL = "https://api.example.com" static let apiVersion = "/api/v1" static let timeout: TimeInterval = 30.0 } // MARK: - Storage Keys enum StorageKeys { static let accessToken = "access_token" static let refreshToken = "refresh_token" static let userData = "user_data" } // MARK: - Design System enum DesignSystem { // Spacing static let spacingXS: CGFloat = 4 static let spacingSM: CGFloat = 8 static let spacingMD: CGFloat = 16 static let spacingLG: CGFloat = 24 // Corner Radius static let cornerRadiusSM: CGFloat = 8 static let cornerRadiusMD: CGFloat = 12 static let cornerRadiusLG: CGFloat = 16 } ``` --- ## Phase 2: Architecture (MVVM + DI) / Kiến Trúc **Goal**: Thiết lập MVVM pattern với Swift Concurrency ### ViewModel Pattern (BẮT BUỘC) ```swift // ViewModels/SomeViewModel.swift import SwiftUI import Combine /// ViewModel for SomeView /// ViewModel cho SomeView @MainActor final class SomeViewModel: ObservableObject { // MARK: - Published Properties /// Current items /// Các items hiện tại @Published private(set) var items: [Item] = [] /// Loading state /// Trạng thái đang tải @Published private(set) var isLoading = false /// Error message /// Thông báo lỗi @Published var errorMessage: String? // MARK: - Dependencies private let service: SomeServiceProtocol // MARK: - Init /// Initialize with dependencies /// Khởi tạo với dependencies init(service: SomeServiceProtocol = SomeService.shared) { self.service = service } // MARK: - Public Methods /// Load items from service /// Tải items từ service func loadItems() async { isLoading = true errorMessage = nil do { items = try await service.fetchItems() } catch { errorMessage = error.localizedDescription } isLoading = false } } ``` ### Service Pattern ```swift // Services/SomeService.swift /// Service protocol for DI /// Protocol service cho DI protocol SomeServiceProtocol { func fetchItems() async throws -> [Item] } /// Main service implementation /// Implementation service chính final class SomeService: SomeServiceProtocol { /// Shared singleton /// Singleton dùng chung static let shared = SomeService() private init() {} func fetchItems() async throws -> [Item] { // Implementation } } ``` ### Dependency Injection via Init ```swift // ✅ GOOD: Protocol-based DI final class HomeViewModel: ObservableObject { private let authManager: AuthManagerProtocol private let apiService: APIServiceProtocol init( authManager: AuthManagerProtocol = AuthManager.shared, apiService: APIServiceProtocol = APIService.shared ) { self.authManager = authManager self.apiService = apiService } } // Testing let mockAuth = MockAuthManager() let viewModel = HomeViewModel(authManager: mockAuth) ``` --- ## Phase 3: UI & Navigation / Giao Diện & Điều Hướng **Goal**: Xây dựng UI với TabView và NavigationStack ### TabView Navigation ```swift // ContentView.swift struct ContentView: View { /// Selected tab @State private var selectedTab: Tab = .home /// Tab enumeration enum Tab: String, CaseIterable { case home, explore, profile var title: String { switch self { case .home: return "Home" case .explore: return "Explore" case .profile: return "Profile" } } var icon: String { switch self { case .home: return "house" case .explore: return "magnifyingglass" case .profile: return "person" } } } var body: some View { TabView(selection: $selectedTab) { HomeView() .tabItem { Label(Tab.home.title, systemImage: Tab.home.icon) } .tag(Tab.home) ExploreView() .tabItem { Label(Tab.explore.title, systemImage: Tab.explore.icon) } .tag(Tab.explore) ProfileView() .tabItem { Label(Tab.profile.title, systemImage: Tab.profile.icon) } .tag(Tab.profile) } } } ``` ### NavigationStack with Path ```swift // Views/Home/HomeView.swift struct HomeView: View { @StateObject private var viewModel = HomeViewModel() @State private var navigationPath = NavigationPath() var body: some View { NavigationStack(path: $navigationPath) { List(viewModel.items) { item in NavigationLink(value: item) { ItemRow(item: item) } } .navigationTitle("Home") .navigationDestination(for: Item.self) { item in ItemDetailView(item: item) } .task { await viewModel.loadItems() } } } } ``` ### Auth State Conditional UI ```swift // ContentView.swift with Auth State struct ContentView: View { @StateObject private var authManager = AuthManager.shared var body: some View { Group { switch authManager.authState { case .unknown: ProgressView() case .unauthenticated: AuthContainerView() case .authenticated: MainTabView() } } .task { await authManager.initialize() } } } ``` --- ## Phase 4: Platform & Extensions / Nền Tảng & Extensions ### String Extensions ```swift // Core/Extensions/String+Extensions.swift extension String { /// Localized string /// Chuỗi đã bản địa hóa var localized: String { NSLocalizedString(self, comment: "") } /// Email validation /// Kiểm tra email hợp lệ var isValidEmail: Bool { let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: self) } /// Trimmed string /// Chuỗi đã trim var trimmed: String { trimmingCharacters(in: .whitespacesAndNewlines) } } ``` ### View Extensions ```swift // Core/Extensions/View+Extensions.swift extension View { /// Apply modifier conditionally /// Áp dụng modifier có điều kiện @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { if condition { transform(self) } else { self } } /// Hide keyboard /// Ẩn bàn phím func hideKeyboard() { UIApplication.shared.sendAction( #selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil ) } } ``` --- ## Common Mistakes / Lỗi Thường Gặp | Mistake | Problem | Solution | |---------|---------|----------| | Logic in View | Hard to test | Move to ViewModel | | Missing `@MainActor` | Thread issues | Add to ViewModel class | | Force unwrap `!` | Crashes | Use `if let`, `guard let` | | Singleton abuse | Hard to test | Protocol-based DI | | No loading states | Bad UX | Add `isLoading` property | | Hardcoded strings | No i18n | Use `String.localized` | ## Quick Reference / Tham Chiếu Nhanh | Category | Standard | |----------|----------| | Architecture | MVVM + Protocol DI | | ViewModel | `@MainActor final class` + `ObservableObject` | | State | `@Published`, `@State`, `@StateObject` | | Navigation | `NavigationStack` + `NavigationPath` | | Tabs | `TabView` with enum-based `Tab` | | Async | `async/await`, `.task {}` modifier | | Constants | Enum-based (no instance) | | Comments | Bilingual EN/VI | ## Resources / Tài Nguyên - [Swift Networking](../swift-networking/SKILL.md) - HTTP client patterns - [Swift Security](../swift-security/SKILL.md) - Keychain & Auth - [Swift UI Components](../swift-ui-components/SKILL.md) - Reusable components - [Apple SwiftUI Docs](https://developer.apple.com/documentation/swiftui/) - [Swift Concurrency](https://developer.apple.com/documentation/swift/concurrency) - [Project Rules](../project-rules/SKILL.md) - GoodGo coding standards