Bỏ qua để đến nội dung

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.

  • 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.
  • 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ả.

Áp dụng Holistic-Driven Development cho cả UI:

  1. Bắt đầu từ CourseView, dùng hardcoded data cho sub-views.
  2. Dần thay thế bằng real View, từng phần một.

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.

Cùng một View có thể implement theo hai paradigm:

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) }
)
}
}
}
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

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.

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.