SwiftUI: Xay dung giao dien hien dai cho iOS

Huong dan xay dung giao dien hien dai voi SwiftUI: cu phap khai bao, thanh phan, hieu ung dong va cac phuong phap tot nhat cho iOS 18.

Huong dan SwiftUI de xay dung giao dien iOS hien dai

SwiftUI đã thay đổi hoàn toàn cách xây dựng giao diện trên các nền tảng Apple. Với cú pháp khai báo và khả năng tích hợp native, framework này cho phép tạo ra các ứng dụng tinh tế với lượng mã ít hơn đáng kể so với trước đây. iOS 18 mang đến những cải tiến quan trọng về hiệu năng cùng nhiều tính năng mới.

Why SwiftUI in 2026?

SwiftUI đã đạt độ trưởng thành trên iOS 18, cung cấp khả năng render được tối ưu hóa, quản lý bộ nhớ tốt hơn và tích hợp UIKit đơn giản hơn. Đây là tiêu chuẩn cho các ứng dụng iOS mới.

Hiểu về mô hình khai báo

Trước khi viết mã, cần hiểu điều gì làm cho SwiftUI khác biệt. Với UIKit (framework cũ hơn), lập trình viên phải chỉ dẫn iOS cách thức xây dựng giao diện từng bước một: "tạo một nhãn, đặt nó ở đây, đổi màu khi người dùng nhấn". Đây là mô hình mệnh lệnh (imperative).

SwiftUI hoạt động khác: lập trình viên mô tả cái gì cần hiển thị, và framework xử lý phần còn lại. Sự khác biệt có thể so sánh với điều hướng GPS: mô hình mệnh lệnh đưa ra chỉ dẫn rẽ từng bước, trong khi mô hình khai báo chỉ cần nêu đích đến.

View đầu tiên

Trong SwiftUI, mọi phần tử giao diện đều là một View. View là một struct mô tả nội dung cần hiển thị trên màn hình. Dưới đây là màn hình đầu tiên với tiêu đề, phụ đề và nút bấm:

ContentView.swiftswift
import SwiftUI

// Each screen is a struct that implements the View protocol
struct ContentView: View {

    // The "body" property describes what the view displays
    // "some View" means "a type of View, but Swift figures it out automatically"
    var body: some View {

        // VStack = "Vertical Stack": stacks elements vertically
        // spacing: 20 = 20 points of space between each element
        VStack(spacing: 20) {

            // A simple text with style modifiers
            Text("Welcome to SharpSkill")
                .font(.largeTitle)      // Large title font
                .fontWeight(.bold)      // Bold text

            Text("Prepare for your iOS interviews")
                .font(.subheadline)     // Smaller font
                .foregroundColor(.secondary)  // Gray secondary color

            // Button with an action (closure) and a label
            Button("Get Started") {
                print("Button tapped!")
            }
            .buttonStyle(.borderedProminent)  // Filled blue button style
        }
        .padding()  // Adds margins around the VStack
    }
}

Cần chú ý cấu trúc: đầu tiên khai báo những gì cần hiển thị (văn bản, nút), sau đó sắp xếp các phần tử theo chiều dọc (VStack), và áp dụng kiểu dáng thông qua modifier (.font(), .padding()).

Điểm mấu chốt: Trong SwiftUI, các đối tượng giao diện không được "tạo ra" thủ công mà được mô tả giao diện mong muốn. SwiftUI tự quản lý việc tạo, cập nhật và hủy các phần tử thực tế.

Modifier: Biến đổi View

Modifier là các phương thức được nối chuỗi sau một view để biến đổi nó. Có thể hình dung chúng như các bộ lọc được áp dụng lần lượt. Thứ tự rất quan trọng vì mỗi modifier tạo ra một view mới bao bọc view trước đó.

Ví dụ minh họa tại sao thứ tự lại quan trọng:

ModifiersOrder.swiftswift
// Example 1: padding THEN background
Text("SwiftUI")
    .padding()           // 1. Adds 16pt of space around the text
    .background(.blue)   // 2. Blue background covers text + padding
    .foregroundColor(.white)
// Result: white text on blue rectangle with margins

// Example 2: background THEN padding (reversed order!)
Text("SwiftUI")
    .background(.blue)   // 1. Tight blue background around text only
    .padding()           // 2. Transparent padding around blue background
    .foregroundColor(.white)
// Result: white text on small blue rectangle, surrounded by empty space

Sự khác biệt nằm ở chỗ: trường hợp đầu tiên, padding nằm "bên trong" background. Trường hợp thứ hai, padding nằm "bên ngoài". Chi tiết tuy nhỏ nhưng cực kỳ quan trọng để làm chủ bố cục.

Tổ chức giao diện với Stacks

SwiftUI cung cấp ba container chính để tổ chức các view. Có thể hình dung chúng như những chiếc hộp sắp xếp nội dung theo các cách khác nhau.

VStack, HStack và ZStack

  • VStack (Vertical Stack): xếp các phần tử từ trên xuống dưới
  • HStack (Horizontal Stack): xếp các phần tử từ trái sang phải
  • ZStack (Z-axis Stack): chồng các phần tử lên nhau

Hãy xây dựng một thẻ hồ sơ người dùng kết hợp cả ba loại stack. Mục tiêu: hiển thị ảnh với huy hiệu xác minh, tên và vai trò của người dùng.

ProfileCard.swiftswift
struct ProfileCard: View {
    var body: some View {
        // Main HStack: photo on left, info on right
        HStack(spacing: 16) {

            // ZStack to overlay the badge on the photo
            ZStack(alignment: .bottomTrailing) {
                // Profile image (circle)
                Image("avatar")
                    .resizable()
                    .frame(width: 60, height: 60)
                    .clipShape(Circle())

                // Green "verified" badge at bottom right
                Image(systemName: "checkmark.circle.fill")
                    .foregroundColor(.green)
                    .background(Circle().fill(.white))  // White circle behind
            }

            // VStack to stack name and role vertically
            VStack(alignment: .leading, spacing: 4) {
                Text("Marie Dupont")
                    .font(.headline)

                Text("iOS Developer")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }

            // Spacer pushes everything to the left
            Spacer()

            // Chevron on right indicates it's tappable
            Image(systemName: "chevron.right")
                .foregroundColor(.gray)
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(12)
    }
}

Thủ thuật chính là Spacer(): một phần tử vô hình chiếm toàn bộ không gian còn lại. Không có Spacer, các phần tử sẽ nằm ở giữa. Có Spacer, nội dung được đẩy sang trái và chevron giữ nguyên vị trí bên phải.

Xcode Tip

Sử dụng ⌘ + click trên bất kỳ view nào trong Xcode để mở visual inspector. Có thể thêm modifier mà không cần gõ mã thủ công.

Quản lý trạng thái với @State và @Binding

Quản lý trạng thái là trái tim của SwiftUI. Trạng thái là bất kỳ dữ liệu nào có thể thay đổi và cần cập nhật giao diện. Khi trạng thái thay đổi, SwiftUI tự động tính toán lại các view bị ảnh hưởng.

@State: Trạng thái cục bộ của View

@State là một property wrapper thông báo cho SwiftUI: "theo dõi biến này và làm mới view khi giá trị thay đổi". Phù hợp cho trạng thái cục bộ của một view.

Hãy tạo một bộ đếm tương tác để hiểu cơ chế hoạt động:

CounterView.swiftswift
struct CounterView: View {
    // @State creates a "source of truth" for this view
    // private because state shouldn't be modified from outside
    @State private var count = 0

    var body: some View {
        VStack(spacing: 30) {
            // This Text updates automatically when count changes
            Text("\(count)")
                .font(.system(size: 72, weight: .bold))

            HStack(spacing: 40) {
                // Decrement button
                Button(action: {
                    count -= 1  // Modifies state → view refreshes
                }) {
                    Image(systemName: "minus.circle.fill")
                        .font(.largeTitle)
                }

                // Increment button
                Button(action: {
                    count += 1
                }) {
                    Image(systemName: "plus.circle.fill")
                        .font(.largeTitle)
                }
            }
        }
    }
}

Khi nhấn nút, giá trị count thay đổi. SwiftUI phát hiện sự thay đổi này và thực thi lại body để cập nhật hiển thị. Không cần cập nhật nhãn thủ công — mọi thứ diễn ra tự động.

@Binding: Chia sẻ trạng thái giữa các View

Đôi khi view con cần thay đổi trạng thái của view cha. Đó là lúc @Binding phát huy tác dụng: tạo kết nối hai chiều đến một @State hiện có.

Dưới đây là ví dụ cụ thể: trường nhập tên người dùng với xác thực theo thời gian thực.

UsernameValidation.swiftswift
// Parent view: owns the state
struct SignupForm: View {
    @State private var username = ""       // Source of truth
    @State private var isValid = false     // Validation state

    var body: some View {
        VStack(spacing: 20) {
            // We pass BINDINGS (with $) to the child view
            UsernameField(username: $username, isValid: $isValid)

            Button("Create Account") {
                // Submit the form
            }
            .disabled(!isValid)  // Disabled if invalid
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

View con nhận binding và có thể thay đổi chúng:

swift
// Child view: receives and modifies state via @Binding
struct UsernameField: View {
    @Binding var username: String   // Connection to parent's @State
    @Binding var isValid: Bool

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            TextField("Username", text: $username)
                .textFieldStyle(.roundedBorder)
                .onChange(of: username) { oldValue, newValue in
                    // Validation: at least 3 characters
                    isValid = newValue.count >= 3
                }

            // Visual feedback
            HStack {
                Image(systemName: isValid ? "checkmark.circle" : "xmark.circle")
                Text("Minimum 3 characters")
            }
            .font(.caption)
            .foregroundColor(isValid ? .green : .red)
        }
    }
}

Khi người dùng nhập vào TextField, username được thay đổi thông qua binding. View cha nhận biết thay đổi này và có thể sử dụng. Đây là giao tiếp hai chiều rõ ràng.

Warning

Chỉ nên sử dụng @State cho trạng thái đơn giản và cục bộ. Đối với dữ liệu được chia sẻ giữa nhiều màn hình hoặc logic phức tạp, nên dùng @Observable (iOS 17+) hoặc các mẫu MVVM.

Sẵn sàng chinh phục phỏng vấn iOS?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Hiển thị danh sách động

Danh sách xuất hiện ở khắp nơi trong các ứng dụng di động. SwiftUI cung cấp List để hiển thị các tập hợp dữ liệu với kiểu dáng native iOS (đường phân cách, thao tác vuốt, v.v.).

Tạo danh sách đơn giản

Để hiển thị danh sách, cần hai thứ: dữ liệu và cách nhận dạng chúng. Giao thức Identifiable cho phép SwiftUI biết view nào tương ứng với dữ liệu nào.

Đầu tiên, định nghĩa mô hình dữ liệu:

Models/Interview.swiftswift
// Identifiable lets SwiftUI track each element
struct Interview: Identifiable {
    let id = UUID()           // Auto-generated unique identifier
    let technology: String
    let difficulty: String
    let questionCount: Int
}

Bây giờ hãy tạo danh sách. Ý tưởng là duyệt qua dữ liệu bằng ForEach và tạo một hàng cho mỗi phần tử:

InterviewListView.swiftswift
struct InterviewListView: View {
    // Data to display (in reality, this would come from an API)
    @State private var interviews = [
        Interview(technology: "iOS", difficulty: "Intermediate", questionCount: 25),
        Interview(technology: "Android", difficulty: "Advanced", questionCount: 30),
        Interview(technology: "React", difficulty: "Beginner", questionCount: 20)
    ]

    var body: some View {
        // NavigationStack enables the navigation bar
        NavigationStack {
            List {
                // ForEach iterates over each interview
                // Thanks to Identifiable, no need to specify id:
                ForEach(interviews) { interview in
                    InterviewRow(interview: interview)
                }
            }
            .navigationTitle("My Interviews")
        }
    }
}

Và đây là view cho mỗi hàng, được tách ra thành component riêng để dễ đọc:

InterviewRow.swiftswift
struct InterviewRow: View {
    let interview: Interview

    var body: some View {
        HStack {
            // Left column: title and subtitle
            VStack(alignment: .leading, spacing: 4) {
                Text(interview.technology)
                    .font(.headline)

                Text(interview.difficulty)
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }

            Spacer()

            // Badge with question count
            Text("\(interview.questionCount) Q")
                .font(.caption)
                .fontWeight(.medium)
                .padding(.horizontal, 8)
                .padding(.vertical, 4)
                .background(Color.blue.opacity(0.1))
                .cornerRadius(8)
        }
        .padding(.vertical, 4)
    }
}

Việc tách InterviewRow thành struct riêng giúp mã dễ đọc hơn và có thể tái sử dụng. Đây là một trong những thực hành tốt nhất của SwiftUI.

Thêm thao tác: Xóa và sắp xếp lại

Danh sách iOS hỗ trợ native thao tác xóa (vuốt trái) và sắp xếp lại (kéo thả). SwiftUI làm cho điều này trở nên đơn giản với .onDelete.onMove:

InterviewListView.swift (enhanced version)swift
struct InterviewListView: View {
    @State private var interviews = [/* ... data ... */]

    var body: some View {
        NavigationStack {
            List {
                ForEach(interviews) { interview in
                    InterviewRow(interview: interview)
                }
                // Swipe to delete
                .onDelete(perform: deleteInterview)
                // Drag and drop to reorder
                .onMove(perform: moveInterview)
            }
            .navigationTitle("My Interviews")
            .toolbar {
                // "Edit" button that activates edit mode
                EditButton()
            }
        }
    }

    // Deletes elements at specified indices
    private func deleteInterview(at offsets: IndexSet) {
        interviews.remove(atOffsets: offsets)
    }

    // Moves elements from one position to another
    private func moveInterview(from source: IndexSet, to destination: Int) {
        interviews.move(fromOffsets: source, toOffset: destination)
    }
}

Chỉ với 4 dòng mã bổ sung (.onDelete, .onMove, EditButton và hai hàm), danh sách trở nên hoàn toàn tương tác. Đó chính là sức mạnh của SwiftUI.

Tạo hiệu ứng động cho giao diện

SwiftUI xử lý hiệu ứng động rất xuất sắc. Khác với UIKit vốn đòi hỏi nhiều mã cho hiệu ứng, ở đây mọi thứ đều mang tính khai báo: chỉ cần mô tả trạng thái cuối cùng và SwiftUI sẽ tạo hiệu ứng chuyển đổi.

Hiệu ứng ngầm với withAnimation

Cách đơn giản nhất để tạo hiệu ứng là bọc thay đổi trạng thái trong withAnimation. SwiftUI phát hiện những gì thay đổi và tự động tạo hiệu ứng cho các thuộc tính trực quan bị ảnh hưởng.

AnimatedCard.swiftswift
struct AnimatedCard: View {
    @State private var isExpanded = false

    var body: some View {
        VStack {
            RoundedRectangle(cornerRadius: 20)
                .fill(.blue)
                // Dimensions change based on state
                .frame(
                    width: isExpanded ? 300 : 150,
                    height: isExpanded ? 200 : 100
                )

            Button(isExpanded ? "Collapse" : "Expand") {
                // withAnimation animates ALL visual changes
                // resulting from this state change
                withAnimation(.spring(response: 0.5, dampingFraction: 0.7)) {
                    isExpanded.toggle()
                }
            }
            .padding(.top)
        }
    }
}

Khi nhấn nút, isExpanded chuyển đổi. Nhờ withAnimation, thay đổi kích thước hình chữ nhật được tạo hiệu ứng với hiệu ứng lò xo. Không cần chỉ định cụ thể phần nào cần tạo hiệu ứng — SwiftUI tự xác định.

Transition: Hiệu ứng xuất hiện và biến mất

Transition xác định cách một view xuất hiện hoặc biến mất. Mặc định là hiệu ứng mờ dần (opacity), nhưng có thể tùy chỉnh:

TransitionDemo.swiftswift
struct TransitionDemo: View {
    @State private var showDetails = false

    var body: some View {
        VStack(spacing: 20) {
            Button("Show Details") {
                withAnimation(.easeInOut(duration: 0.3)) {
                    showDetails.toggle()
                }
            }

            // This view appears/disappears with a transition
            if showDetails {
                DetailCard()
                    // Asymmetric transition: different for entry and exit
                    .transition(
                        .asymmetric(
                            insertion: .scale.combined(with: .opacity),  // Entry: zoom + fade
                            removal: .slide                               // Exit: slide
                        )
                    )
            }
        }
    }
}

struct DetailCard: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Interview Details")
                .font(.headline)
            Text("25 questions • 45 minutes")
                .foregroundColor(.secondary)
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(16)
    }
}

Thẻ xuất hiện với hiệu ứng phóng to từ trung tâm (scale + opacity) và biến mất bằng cách trượt sang bên (slide). Những hiệu ứng vi mô này làm cho giao diện sống động và chuyên nghiệp.

Performance

SwiftUI tự động tối ưu hóa hiệu ứng động. Nên sử dụng .spring() để có cảm giác tự nhiên, và tránh hiệu ứng quá dài (> 0,5 giây) vì chúng có thể gây khó chịu cho người dùng.

Tải dữ liệu bất đồng bộ

Trong các ứng dụng thực tế, dữ liệu thường đến từ API. Swift Concurrency (async/await) tích hợp hoàn hảo với SwiftUI thông qua modifier .task.

Mẫu Loading / Error / Success

Đây là mẫu tiêu chuẩn để hiển thị dữ liệu từ API. Ba trạng thái được xử lý: đang tải, lỗi và thành công.

AsyncDataView.swiftswift
struct AsyncDataView: View {
    @State private var questions: [Question] = []
    @State private var isLoading = true
    @State private var errorMessage: String?

    var body: some View {
        Group {
            if isLoading {
                // State: loading in progress
                ProgressView("Loading...")

            } else if let error = errorMessage {
                // State: error
                VStack(spacing: 16) {
                    Image(systemName: "exclamationmark.triangle")
                        .font(.largeTitle)
                        .foregroundColor(.orange)
                    Text(error)
                    Button("Retry") {
                        Task { await loadQuestions() }
                    }
                }

            } else {
                // State: success, display data
                List(questions) { question in
                    Text(question.title)
                }
            }
        }
        // .task runs automatically when the view appears
        .task {
            await loadQuestions()
        }
        // Pull-to-refresh
        .refreshable {
            await loadQuestions()
        }
    }

    private func loadQuestions() async {
        isLoading = true
        errorMessage = nil

        do {
            // Async API call
            questions = try await QuestionService.shared.fetchQuestions()
        } catch {
            errorMessage = "Unable to load questions"
        }

        isLoading = false
    }
}

Modifier .task đóng vai trò then chốt: khởi chạy tác vụ bất đồng bộ khi view xuất hiện và tự động hủy khi view biến mất. Rò rỉ bộ nhớ hoàn toàn được loại trừ.

Kết luận

SwiftUI đã trở thành phần không thể thiếu trong phát triển iOS hiện đại. Với sự ra mắt của iOS 18, framework này đạt mức độ trưởng thành hoàn toàn phù hợp cho các ứng dụng production.

Những điểm chính

  • Mô hình khai báo: mô tả kết quả mong muốn thay vì cách thức xây dựng
  • @State và @Binding: quản lý trạng thái phản ứng và truyền giữa các view
  • Stacks: kết hợp VStack, HStack và ZStack cho bố cục linh hoạt
  • List: hiển thị tập hợp với mã tối thiểu và tương tác native
  • Hiệu ứng động: sử dụng withAnimation cho chuyển đổi mượt mà tự động
  • Async/await: tải dữ liệu với .task và xử lý trạng thái loading/error

Danh sách kiểm tra

  • Hiểu sự khác biệt giữa mệnh lệnh (UIKit) và khai báo (SwiftUI)
  • Thành thạo modifier và thứ tự áp dụng
  • Biết khi nào dùng @State, @Binding và @Observable
  • Xây dựng bố cục với Stacks
  • Triển khai danh sách với các thao tác (xóa, di chuyển)
  • Tạo hiệu ứng thay đổi trạng thái với withAnimation

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

SwiftUI mở ra cơ hội xây dựng các ứng dụng tinh tế trên toàn bộ hệ sinh thái Apple. Cách tốt nhất để học là thực hành: hãy tạo một dự án cá nhân nhỏ và thử nghiệm với từng khái niệm trong bài viết này.

Thẻ

#swiftui
#ios
#swift
#ui
#apple

Chia sẻ

Bài viết liên quan