Triển khai UI thực tế
Đã bàn nhiều lý thuyết về UI. Bây giờ là lúc biến lý thuyết thành code thật.
Chương này không dạy cách viết UI — giả sử bạn đã biết. Focus là tại sao đưa ra các quyết định khi implement, và cách ship nhanh nhờ mindset đúng.
Nguyên tắc: Make it work, then make it pretty.
1. Chọn approach
Phần tiêu đề “1. Chọn approach”Top-down
Phần tiêu đề “Top-down”- Bắt đầu từ View tổng (
CourseView), code hết lên một file. - Sau đó extract các sub-views ra.
- Ưu điểm: Thấy kết quả nhanh.
- Nhược điểm: Dễ tạo ra massive view, khó tách sau.
Bottom-up
Phần tiêu đề “Bottom-up”- Code các View nhỏ trước (button, label, card…).
- Sau đó compose thành UI hoàn chỉnh.
- Ưu điểm: Mỗi View nhỏ gọn, reusable.
- Nhược điểm: Phải chờ lâu mới thấy kết quả.
✅ Recommend: Kết hợp — Holistic approach
Phần tiêu đề “✅ Recommend: Kết hợp — Holistic approach”Áp dụng Holistic-Driven Development cho cả UI:
- Bắt đầu từ
CourseView, dùng hardcoded data cho sub-views. - Dần thay thế bằng real View, từng phần một.
2. Giữ View body high-level
Phần tiêu đề “2. Giữ View body high-level”Khi code declarative UI (SwiftUI, Compose), giữ phần body ngắn gọn và ở mức cao:
struct CourseView: View { let course: Course
var body: some View { ScrollView { ThumbDescriptionView( title: course.tutor.name, description: course.tutor.handle ) CalloutView(message: course.tutor.calloutMessage) ScheduleView(event: course.nextEvent) SelectionView(items: course.todoItems) } }}Body chỉ làm nhiệm vụ assemble — không chứa logic phức tạp.
3. Declarative vs. Imperative
Phần tiêu đề “3. Declarative vs. Imperative”Cùng một View có thể implement theo hai paradigm:
Declarative (SwiftUI, Compose)
Phần tiêu đề “Declarative (SwiftUI, Compose)”struct SelectionView: View { let items: [SelectionItem] @State private var selectedIds: Set<String> = []
var body: some View { ForEach(items) { item in SelectionViewItem( title: item.title, isSelected: selectedIds.contains(item.id), onTap: { toggleSelection(item.id) } ) } }}Imperative (UIKit, XML)
Phần tiêu đề “Imperative (UIKit, XML)”class SelectionViewController: UIViewController { private var items: [SelectionItem] = [] private var selectedIds: Set<String> = []
func updateItems(_ newItems: [SelectionItem]) { self.items = newItems tableView.reloadData() }}Key insight: Dù paradigm nào, system design principles vẫn giống nhau:
- View không biết về business logic
- Tách data khỏi UI
- Compose small views thay vì build massive views
4. View Primitives implementation
Phần tiêu đề “4. View Primitives implementation”View Primitives nên cực kỳ đơn giản:
struct TextButton: View { let title: String let action: () -> Void
var body: some View { Button(action: action) { Text(title) } }}Nếu một View Primitive bắt đầu có state management hay complex logic, đó là dấu hiệu nó đang trở thành View Component — hãy classify lại.
5. Navigation ownership
Phần tiêu đề “5. Navigation ownership”Feature View không nên tự navigate. Thay vào đó, expose callback:
struct CourseView: View { let onMessageTapped: () -> Void let onJoinCallTapped: () -> Void}Coordinator, Router, hay NavigationStack sẽ quyết định navigate đến đâu. Nhờ đó, CourseView có thể được đặt ở bất kỳ đâu trong app.
Kết luận: Ship nhanh bằng holistic approach — dùng hardcoded data để assemble nhanh, dần thay bằng real views. Giữ body high-level, và để feature view không biết về navigation.