13 KiB
13 KiB
name, description, compatibility, metadata
| name | description | compatibility | metadata | ||||||
|---|---|---|---|---|---|---|---|---|---|
| swift-enterprise-architect | 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. | Swift 5.9+, iOS 17+, macOS 14+, SwiftUI |
|
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
// 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)
// 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
// 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
// ✅ 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
// 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
// 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
// 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
// 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
// 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 - HTTP client patterns
- Swift Security - Keychain & Auth
- Swift UI Components - Reusable components
- Apple SwiftUI Docs
- Swift Concurrency
- Project Rules - GoodGo coding standards