450 lines
13 KiB
Markdown
450 lines
13 KiB
Markdown
---
|
|
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`<Content: View>(_ 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
|