SwiftUI Cơ bản: Views, Modifiers và Điều Hướng – Bước Đầu Xây Dựng Giao Diện Hiện Đại

Lộ trình học Lập trình viên iOS 2025 tiếp tục với một công nghệ đột phá đã định hình lại cách chúng ta xây dựng giao diện người dùng trên các nền tảng của Apple: SwiftUI. Sau khi đã làm quen với các kiến thức Swift cơ bản và hiểu về khái niệm Lập trình Giao diện Khai báo (Declarative UI), đã đến lúc đi sâu vào những khối xây dựng cốt lõi của SwiftUI. Bài viết này sẽ tập trung vào ba trụ cột chính: Views (Chế độ xem), Modifiers (Bộ điều chỉnh) và Navigation (Điều hướng).

Views: Những Khối Xây Dựng Của Giao Diện Người Dùng

Trong SwiftUI, mọi thứ bạn nhìn thấy trên màn hình đều là một View. Một View có thể là một đoạn văn bản, một hình ảnh, một nút bấm, hoặc thậm chí là một bố cục phức tạp chứa nhiều Views khác. Điểm khác biệt cốt lõi so với UIKit là View trong SwiftUI là cấu trúc (struct) và tuân thủ protocol View. Điều này có nghĩa chúng là các giá trị (value types), giúp đơn giản hóa việc quản lý trạng thái và luồng dữ liệu.

Khi bạn khai báo một View, bạn mô tả *điều gì* bạn muốn hiển thị. SwiftUI sẽ tự động xử lý việc hiển thị và cập nhật View đó khi dữ liệu hoặc trạng thái thay đổi. Đây chính là bản chất của lập trình giao diện khai báo.

Một View đơn giản nhất trong SwiftUI có cấu trúc như sau:


struct MyFirstView: View {
    var body: some View {
        // Nội dung của View được định nghĩa ở đây
        Text("Xin chào, SwiftUI!")
    }
}

Trong ví dụ trên:

  • MyFirstView là tên của cấu trúc View.
  • : View cho biết cấu trúc này tuân thủ protocol View.
  • body: some View là thuộc tính bắt buộc. Nó trả về một kiểu dữ liệu ẩn (some View) thể hiện nội dung trực quan của View này.
  • Text("Xin chào, SwiftUI!") là một View tích hợp sẵn để hiển thị văn bản.

Các View Tích Hợp Sẵn Phổ Biến

SwiftUI cung cấp một bộ sưu tập phong phú các View tích hợp sẵn mà bạn có thể sử dụng ngay:

  • Hiển thị Văn bản và Hình ảnh:
    • Text("..."): Hiển thị một chuỗi văn bản.
    • Image("...") hoặc Image(systemName: "..."): Hiển thị hình ảnh từ asset catalog hoặc biểu tượng SF Symbols.
  • Điều khiển:
    • Button { ... } label: { ... }: Một nút bấm có thể tương tác. Phần label định nghĩa nội dung trực quan của nút (có thể là Text, Image, hoặc tổ hợp các View khác).
    • TextField("...", text: $...): Trường nhập văn bản một dòng.
    • SecureField("...", text: $...): Trường nhập văn bản an toàn (ẩn ký tự).
    • Toggle("...", isOn: $...): Công tắc chuyển đổi trạng thái Bật/Tắt.
    • Slider(value: $..., in: ...): Thanh trượt chọn giá trị.
    • Stepper(value: $..., in: ...) { ... }: Bộ đếm tăng/giảm.
    • Picker("...", selection: $...) { ... }: Bộ chọn giá trị từ danh sách.
    • DatePicker(selection: $...) { ... }: Bộ chọn ngày giờ.

Kết Hợp Views (Composing Views)

Sức mạnh của SwiftUI nằm ở khả năng kết hợp các View nhỏ hơn để tạo ra View lớn hơn, phức tạp hơn. Các View container đóng vai trò quan trọng trong việc sắp xếp bố cục:

  • VStack { ... }: Sắp xếp các View con theo chiều dọc (Vertical Stack).
  • HStack { ... }: Sắp xếp các View con theo chiều ngang (Horizontal Stack).
  • ZStack { ... }: Xếp chồng các View con lên nhau (Z-axis Stack), giống như các lớp trong Photoshop hoặc Sketch.
  • ScrollView { ... }: Cho phép cuộn nội dung. Có thể chứa VStack hoặc HStack bên trong để cuộn theo chiều dọc hoặc ngang.
  • List { ... }: Hiển thị dữ liệu dưới dạng danh sách, tương tự UITableView trong UIKit nhưng đơn giản hơn nhiều.

Ví dụ về kết hợp Views:


struct ContentView: View {
    var body: some View {
        VStack { // Sắp xếp theo chiều dọc
            Text("Tiêu đề")
                .font(.largeTitle) // Chúng ta sẽ nói về Modifiers sau!
            HStack { // Sắp xếp theo chiều ngang
                Image(systemName: "star.fill")
                    .foregroundColor(.yellow)
                Text("Đánh giá 5 sao")
                    .font(.headline)
            }
            Spacer() // Đẩy nội dung lên phía trên
            Button("Chi tiết") {
                // Hành động khi nút được nhấn
            }
        }
        .padding() // Thêm khoảng đệm xung quanh VStack
    }
}

Ở đây, chúng ta có một VStack chứa một Text, một HStack (chứa ImageText), một Spacer và một Button. HStack nằm bên trong VStack, cho thấy cách các View có thể được lồng vào nhau để xây dựng cấu trúc UI phức tạp.

Modifiers: Thay Đổi Giao Diện và Hành Vi Của Views

Nếu Views định nghĩa *điều gì* được hiển thị, thì Modifiers định nghĩa *nó trông như thế nào* hoặc *nó hoạt động ra sao*. Modifiers là các phương thức được gọi trên một View và trả về một View *mới* đã được áp dụng sự thay đổi. Điều này phù hợp với bản chất value types của Views trong SwiftUI.

Cú pháp sử dụng Modifier rất đơn giản: bạn gọi phương thức modifier sau View mà bạn muốn áp dụng, sử dụng dấu chấm.


Text("Văn bản được định dạng")
    .foregroundColor(.blue) // Modifier thay đổi màu chữ
    .font(.title)         // Modifier thay đổi cỡ chữ
    .padding()            // Modifier thêm khoảng đệm

Mỗi lần gọi một modifier, nó trả về một View mới. Điều này cho phép bạn “chuỗi” (chain) nhiều Modifiers lại với nhau để áp dụng nhiều thay đổi lên cùng một View.

Ví dụ về chaining Modifiers:


Image(systemName: "heart.fill")
    .resizable() // Cho phép thay đổi kích thước hình ảnh
    .aspectRatio(contentMode: .fit) // Giữ tỷ lệ khung hình khi thay đổi kích thước
    .frame(width: 100, height: 100) // Đặt kích thước khung cố định
    .foregroundColor(.red) // Đặt màu đỏ
    .shadow(radius: 10) // Thêm đổ bóng

Thứ tự của các Modifiers trong chuỗi là quan trọng, vì mỗi Modifier hoạt động trên kết quả của Modifier đứng trước nó. Ví dụ, nếu bạn đặt .padding() trước .background(), khoảng đệm sẽ nằm *bên trong* nền. Ngược lại, nếu đặt .padding() sau .background(), khoảng đệm sẽ nằm *bên ngoài* nền.

Các Modifiers Phổ Biến

Có hàng trăm Modifiers trong SwiftUI, bao gồm:

  • Thay đổi Kích thước & Bố cục:
    • .frame(width: CGFloat?, height: CGFloat?, alignment: Alignment?): Đặt kích thước khung của View.
    • .padding(Edge.Set, CGFloat?): Thêm khoảng đệm xung quanh View.
    • .offset(x: CGFloat, y: CGFloat): Di chuyển View lệch khỏi vị trí ban đầu.
    • .aspectRatio(contentMode: ContentMode): Giữ tỷ lệ khung hình (.fit hoặc .fill).
  • Thay đổi Màu sắc & Hình thức:
    • .foregroundColor(Color): Đặt màu chữ hoặc màu biểu tượng.
    • .background(View): Đặt nền cho View (có thể là một View khác như Color, Image, hoặc RoundedRectangle).
    • .border(Color, width: CGFloat): Thêm viền cho View.
    • .cornerRadius(CGFloat): Bo tròn góc của View.
    • .shadow(radius: CGFloat, x: CGFloat, y: CGFloat): Thêm đổ bóng.
  • Thay đổi Văn bản & Font:
    • .font(Font): Đặt font cho văn bản.
    • .bold(), .italic(): Đặt kiểu chữ in đậm, in nghiêng.
    • .multilineTextAlignment(TextAlignment): Căn lề văn bản nhiều dòng.
  • Xử lý Sự kiện:
    • .onTapGesture { ... }: Thêm hành động khi View được nhấn.
    • .onChange(of: Value) { ... }: Thực hiện hành động khi một giá trị thay đổi.

Việc kết hợp Views và Modifiers là nền tảng để xây dựng mọi giao diện phức tạp trong SwiftUI. Bạn định nghĩa cấu trúc bằng Views (như VStack, HStack) và sau đó tinh chỉnh ngoại hình và hành vi của từng View bằng các Modifiers.

Navigation: Di Chuyển Giữa Các Màn Hình

Trong hầu hết các ứng dụng di động, người dùng cần di chuyển giữa các màn hình hoặc View khác nhau. SwiftUI cung cấp các thành phần tích hợp sẵn để xử lý điều hướng một cách tự nhiên và hiệu quả.

Thành phần trung tâm cho điều hướng theo kiểu stack (push/pop) là NavigationStack (hoặc NavigationView trên các phiên bản iOS cũ hơn 16). NavigationStack là một View container chứa nội dung màn hình hiện tại và quản lý stack của các màn hình đã được đẩy vào.


struct AppNavigationView: View {
    var body: some View {
        NavigationStack { // Hoặc NavigationView trên iOS 13-15
            ContentView() // Màn hình gốc
                .navigationTitle("Trang Chủ") // Đặt tiêu đề cho màn hình này
        }
    }
}

Trong ví dụ trên, AppNavigationView bọc lấy ContentView bên trong một NavigationStack. Điều này biến ContentView thành màn hình gốc trong stack điều hướng, và nó có thể hiển thị thanh điều hướng với tiêu đề.

Để chuyển sang một màn hình khác, chúng ta sử dụng NavigationLink. NavigationLink là một View có hai phần chính: một *điểm đến* (destination) là View bạn muốn điều hướng tới và một *nhãn* (label) là View hiển thị trên màn hình hiện tại mà người dùng tương tác để kích hoạt điều hướng (ví dụ: một Text, một Image, hoặc một bố cục phức tạp hơn).


struct ContentView: View {
    var body: some View {
        NavigationStack { // Đảm bảo ContentView nằm trong NavigationStack/View
            VStack {
                Text("Màn hình Chính")

                // NavigationLink để đi đến màn hình Chi tiết
                NavigationLink {
                    DetailView() // Điểm đến: DetailView
                } label: {
                    Text("Xem Chi tiết") // Nhãn: Văn bản "Xem Chi tiết"
                }
                .padding()

                // Một NavigationLink khác
                NavigationLink("Đi đến Màn hình Cài đặt") { // Nhãn ngắn gọn
                    SettingsView() // Điểm đến: SettingsView
                }
                .padding()
            }
            .navigationTitle("Ứng Dụng Của Tôi") // Tiêu đề của màn hình này
        }
    }
}

struct DetailView: View {
    var body: some View {
        VStack {
            Text("Đây là Màn hình Chi tiết")
        }
        .navigationTitle("Chi tiết") // Tiêu đề cho màn hình DetailView
    }
}

struct SettingsView: View {
    var body: some View {
        VStack {
            Text("Đây là Màn hình Cài đặt")
        }
        .navigationTitle("Cài đặt") // Tiêu đề cho màn hình SettingsView
    }
}

Khi người dùng nhấn vào “Xem Chi tiết” hoặc “Đi đến Màn hình Cài đặt”, NavigationLink sẽ đẩy DetailView hoặc SettingsView vào stack điều hướng, hiển thị màn hình mới và thêm nút quay lại ở thanh điều hướng.

NavigationStack (iOS 16+) cũng hỗ trợ điều hướng bằng lập trình thông qua ràng buộc dữ liệu (binding) tới một tập hợp các kiểu dữ liệu có thể hashable, cho phép bạn kiểm soát stack điều hướng một cách linh hoạt hơn. Đây là một cải tiến đáng kể so với NavigationView cũ.

Một khía cạnh quan trọng khác của điều hướng là truyền dữ liệu giữa các màn hình. Bạn thường sẽ cần truyền dữ liệu từ màn hình danh sách sang màn hình chi tiết. Điều này được thực hiện bằng cách định nghĩa các thuộc tính trên View đích và truyền dữ liệu vào khi tạo View đó trong NavigationLink hoặc khi đẩy View bằng lập trình. Các thuộc tính này thường được đánh dấu bằng các property wrapper như @State (cho trạng thái cục bộ), @Binding (liên kết đến trạng thái ở View cha), hoặc @ObservableObject / @EnvironmentObject (cho quản lý dữ liệu phức tạp hơn), các khái niệm này sẽ được đề cập sâu hơn trong các bài viết sau về quản lý dữ liệukiến trúc ứng dụng.

So với cách điều hướng dựa trên ViewController và Segues trong UIKit, cách tiếp cận của SwiftUI với NavigationStackNavigationLink thường mang tính khai báo hơn và dễ dàng theo dõi luồng điều hướng trong code.

Kết Hợp Tất Cả: Xây Dựng Một Màn Hình Đơn Giản

Hãy cùng xem một ví dụ kết hợp cả Views, Modifiers và Navigation để tạo một màn hình đơn giản hiển thị danh sách và cho phép xem chi tiết mục được chọn.


import SwiftUI

struct Item: Identifiable {
    let id = UUID()
    let name: String
    let description: String
}

struct ContentView: View {
    let items = [
        Item(name: "iPhone", description: "Một chiếc điện thoại thông minh từ Apple."),
        Item(name: "iPad", description: "Một máy tính bảng mạnh mẽ cho công việc và giải trí."),
        Item(name: "MacBook", description: "Máy tính xách tay hiệu năng cao cho mọi tác vụ."),
        Item(name: "Apple Watch", description: "Đồng hồ thông minh theo dõi sức khỏe và kết nối."),
    ]

    var body: some View {
        NavigationStack {
            List(items) { item in // Sử dụng List để hiển thị danh sách
                NavigationLink {
                    ItemDetailView(item: item) // Điểm đến: ItemDetailView, truyền dữ liệu item
                } label: {
                    ItemRow(item: item) // Nhãn: Custom View ItemRow
                }
            }
            .navigationTitle("Sản Phẩm Apple") // Tiêu đề thanh điều hướng cho màn hình này
        }
    }
}

struct ItemRow: View {
    let item: Item

    var body: some View {
        HStack { // Sắp xếp nội dung hàng theo chiều ngang
            Image(systemName: "cube.fill") // Biểu tượng
                .foregroundColor(.blue)
                .font(.title2)
            VStack(alignment: .leading) { // Sắp xếp văn bản theo chiều dọc, căn trái
                Text(item.name)
                    .font(.headline) // Modifier cỡ chữ
                Text(item.description)
                    .font(.subheadline) // Modifier cỡ chữ phụ
                    .foregroundColor(.gray) // Modifier màu chữ
            }
            Spacer() // Đẩy nội dung sang trái
        }
        .padding(.vertical, 5) // Thêm khoảng đệm dọc cho hàng
    }
}

struct ItemDetailView: View {
    let item: Item

    var body: some View {
        VStack(alignment: .leading, spacing: 10) { // Sắp xếp chi tiết theo chiều dọc
            Text(item.name)
                .font(.largeTitle) // Modifier cỡ chữ lớn
                .bold() // Modifier in đậm

            Text(item.description)
                .font(.body) // Modifier cỡ chữ thân bài

            Spacer() // Đẩy nội dung lên trên
        }
        .padding() // Thêm khoảng đệm cho toàn bộ nội dung chi tiết
        .navigationTitle(item.name) // Tiêu đề thanh điều hướng là tên sản phẩm
        .navigationBarTitleDisplayMode(.inline) // Hiển thị tiêu đề nhỏ gọn
    }
}

// Preview Provider (giúp hiển thị giao diện trong Xcode Preview)
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Trong ví dụ này:

  • Chúng ta định nghĩa cấu trúc Item để mô hình hóa dữ liệu.
  • ContentView sử dụng NavigationStackList để hiển thị danh sách các Item.
  • Mỗi hàng trong List là một NavigationLink.
    • Điểm đến của link là ItemDetailView, được tạo ra và truyền dữ liệu item tương ứng.
    • Nhãn của link là một View tùy chỉnh ItemRow, cũng nhận dữ liệu item để hiển thị tên và mô tả.
  • ItemRow là một View tùy chỉnh sử dụng HStack, VStack, Image, và Text, cùng với các Modifiers như .foregroundColor(), .font(), .padding() để tạo bố cục và định dạng cho mỗi hàng.
  • ItemDetailView là View hiển thị chi tiết của một Item, sử dụng VStack, Text, và các Modifiers như .font(), .bold(), .padding(), .navigationTitle() để hiển thị thông tin chi tiết và đặt tiêu đề cho màn hình.

Ví dụ này minh họa cách bạn kết hợp Views (List, HStack, VStack, Image, Text, NavigationLink), Modifiers (.font(), .foregroundColor(), .padding(), .navigationTitle()), và Navigation (NavigationStack, NavigationLink) để xây dựng một ứng dụng có cấu trúc.

Tóm Lược: Các Khái Niệm Cốt Lõi

Để củng cố kiến thức, dưới đây là bảng tóm tắt về ba khái niệm chính chúng ta đã thảo luận:

Khái niệm Mục đích Ví dụ cơ bản
View Mô tả một phần tử giao diện người dùng trên màn hình. Là cấu trúc (struct) tuân thủ protocol View. Text("Hello"), Image(...), Button(...), VStack { ... }
Modifier Thay đổi giao diện, hành vi hoặc bố cục của một View. Là các phương thức được gọi trên View và trả về một View đã được sửa đổi. .padding(), .foregroundColor(.blue), .font(.title), .frame(...)
Navigation Quản lý luồng di chuyển giữa các màn hình (Views) trong ứng dụng. Sử dụng stack để theo dõi các màn hình hiện tại. NavigationStack { ... }, NavigationLink { DestinationView() } label: { LabelView() }

Việc nắm vững ba khái niệm này là bước khởi đầu quan trọng trên con đường phát triển ứng dụng iOS với SwiftUI. Chúng là nền tảng cho mọi giao diện bạn sẽ xây dựng.

Tiếp Theo Trên Lộ Trình

Bạn đã tìm hiểu về các khối xây dựng cơ bản của SwiftUI: Views, Modifiers và cách điều hướng đơn giản. Đây là những công cụ mạnh mẽ cho phép bạn bắt đầu tạo ra các giao diện người dùng hiện đại một cách nhanh chóng và hiệu quả.

Trong các bài viết tiếp theo của Lộ trình học Lập trình viên iOS 2025, chúng ta sẽ đi sâu hơn vào các chủ đề nâng cao hơn trong SwiftUI, bao gồm quản lý trạng thái và luồng dữ liệu (như @State, @ObservedObject, @EnvironmentObject), làm việc với danh sách và dữ liệu động, xử lý form, và nhiều hơn nữa. Hiểu rõ cách Views, Modifiers và Navigation hoạt động là nền tảng vững chắc để tiếp thu các khái niệm phức tạp hơn đó.

Hãy dành thời gian thực hành bằng cách tạo các View đơn giản, thử nghiệm các Modifiers khác nhau và xây dựng các luồng điều hướng cơ bản. Kinh nghiệm thực tế là cách tốt nhất để củng cố kiến thức này. Chúc bạn thành công trên hành trình làm chủ SwiftUI!

Chỉ mục