SwiftUI — framework khai báo của Apple để xây dựng giao diện người dùng trên tất cả các nền tảng trong hệ sinh thái, được giới thiệu tại WWDC 2019. Khác với UIKit mệnh lệnh với viewDidLoad và cập nhật màn hình thủ công, SwiftUI mô tả UI như một tập hợp các cấu trúc đơn giản tuân theo giao thức View. Theo Swift.org (2025), SwiftUI được sử dụng trong 65% các dự án mới được xuất bản trên App Store. Framework tự động quản lý cập nhật giao diện thông qua cơ chế State và Data Flow — khi dữ liệu thay đổi, View được vẽ lại mà không cần gọi reloadData thủ công.
Những điểm chính
SwiftUI — framework UI khai báo của Apple, khác biệt hoàn toàn so với UIKit. Thay vì tạo bộ điều khiển, view và quản lý vòng đời của chúng thủ công, nhà phát triển mô tả giao diện dưới dạng khai báo: những gì sẽ hiển thị trên màn hình, chứ không phải cách xây dựng nó. SwiftUI dựa trên nguyên tắc phản ứng: giao diện là một hàm của trạng thái. Khi trạng thái thay đổi, SwiftUI tự động tính toán lại body của tất cả các View phụ thuộc và chỉ cập nhật những phần đã thay đổi của màn hình. SwiftUI khả dụng trên iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ và visionOS 1+. Mã SwiftUI đa nền tảng: một tệp hoạt động trên iPhone, iPad, Mac và Apple Watch với những điều chỉnh tối thiểu theo nền tảng. Theo Apple WWDC Session 101 (2024), SwiftUI bao phủ hơn 90% các mẫu UI tiêu chuẩn của App Store.
UIKit — framework mệnh lệnh (2008): nhà phát triển tạo UIViewController, cấu hình subviews trong viewDidLoad, triển khai delegate/datasource cho UITableView và cập nhật màn hình qua reloadData hoặc setNeedsLayout. SwiftUI thay thế bộ điều khiển bằng cấu trúc View đơn giản, delegate bằng bindings và onChange, Auto Layout bằng HStack/VStack/ZStack với các bổ từ (padding, frame, offset). UIKit yêu cầu quản lý bộ nhớ thủ công qua ARC; SwiftUI sử dụng cấu trúc không cần đếm tham chiếu. Hiệu suất của SwiftUI tương đương UIKit: framework sử dụng thuật toán diffing cho tập hợp thay đổi tối thiểu. Tại IT Sectr, SwiftUI được sử dụng cho các dự án mới với mục tiêu iOS 17+; các dự án hỗ trợ iOS 14–15 yêu cầu UIKit do khả năng tương thích hạn chế của SwiftUI.
Trong SwiftUI, giao diện được mô tả thông qua ViewBuilder — một result builder biến đổi tập hợp các View thành một tuple hoặc Group. Các bổ từ (.padding(), .font(), .foregroundColor()) tạo View mới với cài đặt đã sửa đổi thay vì làm thay đổi đối tượng gốc. Mỗi bổ từ trả về một View mới, cho phép xâu chuỗi. ViewBuilder hỗ trợ if/else, switch, ForEach — kết xuất có điều kiện và theo chu kỳ mà không cần bộ điều khiển riêng. View trong SwiftUI là value type (struct), đảm bảo hành vi có thể dự đoán trước và loại bỏ điều kiện tranh đua.
View — một giao thức với một yêu cầu duy nhất: computed property body kiểu some View. Mỗi cấu trúc tuân theo View mô tả phần màn hình của nó trong body. Kiểu some View là kiểu trả về mờ che giấu kiểu cụ thể của View được trả về (xếp chồng VStack, HStack, ZStack, Text, Image, v.v.). Trình biên dịch Swift suy luận kiểu cụ thể tại thời điểm biên dịch, duy trì hiệu suất của các lệnh gọi trực tiếp mà không xóa kiểu.
import SwiftUI
struct GreetingView: View {
var name: String
var body: some View {
VStack(spacing: 12) {
Text("Xin chào, \(name)!")
.font(.largeTitle)
.foregroundColor(.primary)
Text("Chào mừng bạn đến với SwiftUI")
.font(.body)
.foregroundColor(.secondary)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(.ultraThinMaterial)
)
}
}Cấu trúc GreetingView nhận tham số name và hiển thị hai khối văn bản trong một ngăn xếp dọc. Các bổ từ .font, .foregroundColor, .padding và .background cấu hình giao diện. SwiftUI gọi body mỗi khi tham số đầu vào (name) thay đổi — việc vẽ lại chỉ xảy ra cho các phần đã thay đổi. Ví dụ sử dụng RoundedRectangle với .ultraThinMaterial — nền mờ gốc được tích hợp trong SwiftUI.
@State — một property wrapper khai báo trạng thái cục bộ thuộc về một View duy nhất. SwiftUI tự động quản lý bộ nhớ State: khi giá trị thay đổi, body được vẽ lại, nhưng chỉ cho các View sử dụng State đó. State là nguồn chân lý cho các kiểu đơn giản (String, Int, Bool, enum). Không sử dụng @State cho các mô hình dữ liệu phức tạp — hãy sử dụng @StateObject và @ObservedObject để thay thế. State phải là private và được lưu trữ trong chính View, không được truyền giữa các thành phần.
import SwiftUI
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("Đếm: \(count)")
.font(.system(size: 48, weight: .bold))
Button(action: { count += 1 }) {
Label("Tăng", systemImage: "plus.circle")
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}Giá trị count ban đầu = 0. Mỗi lần nhấn nút tăng count; SwiftUI tự động vẽ lại toàn bộ CounterView (tất cả các View). Trong UIKit, một kịch bản tương tự sẽ yêu cầu IBOutlet, IBAction và cập nhật label.text thủ công. @State đảm bảo rằng View chỉ được vẽ lại khi một State cụ thể thay đổi — thuật toán diffing của SwiftUI tìm ra những thay đổi tối thiểu trong cây.
@Binding — một property wrapper tạo kết nối hai chiều giữa View và dữ liệu mà View không sở hữu. Binding là một tham chiếu đến State (hoặc nguồn chân lý khác), cho phép View con đọc và sửa đổi giá trị được lưu trữ trong cha. Binding được ký hiệu bằng tiền tố $: $count truyền Binding<Int> vào View con. Không có Binding, View con không thể sửa đổi dữ liệu của cha — chỉ có thể đọc nó.
import SwiftUI
struct StepperControl: View {
@Binding var value: Int
let range: ClosedRange<Int>
var body: some View {
HStack {
Button(action: { if value > range.lowerBound { value -= 1 } }) {
Image(systemName: "minus.circle")
}
Text("\(value)")
.frame(minWidth: 40)
Button(action: { if value < range.upperBound { value += 1 } }) {
Image(systemName: "plus.circle")
}
}
}
}
struct ParentView: View {
@State private var quantity = 5
var body: some View {
StepperControl(value: $quantity, range: 1...10)
}
}ParentView sở hữu State quantity và truyền Binding qua $quantity. StepperControl có thể thay đổi giá trị, và quantity trong cha tự động đồng bộ hóa. Binding không phải là bản sao dữ liệu, mà là cầu nối đến nguồn chân lý. Sử dụng @Binding cho các điều khiển tùy chỉnh, trình soạn thảo và thành phần tái sử dụng cần sửa đổi dữ liệu của cha.
@StateObject — một property wrapper để tạo và sở hữu một thể hiện của lớp tuân theo ObservableObject. View tạo đối tượng một lần trong vòng đời và được vẽ lại khi các thuộc tính @Published của nó thay đổi. @ObservedObject — một wrapper tương tự, nhưng View không sở hữu đối tượng — đối tượng được tạo và lưu trữ bên ngoài View (được truyền qua bộ khởi tạo). Apple khuyến nghị @StateObject cho nguồn chân lý trong hệ phân cấp View và @ObservedObject cho tiêm phụ thuộc.
import SwiftUI
import Combine
class UserSettings: ObservableObject {
@Published var username: String = "Guest"
@Published var isLoggedIn = false
}
struct ProfileView: View {
@StateObject private var settings = UserSettings()
var body: some View {
VStack {
TextField("Username", text: $settings.username)
.textFieldStyle(.roundedBorder)
Toggle("Logged In", isOn: $settings.isLoggedIn)
if settings.isLoggedIn {
Text("Chào mừng, \(settings.username)!")
.font(.headline)
}
}
.padding()
}
}UserSettings — một ObservableObject với hai thuộc tính @Published. ProfileView sở hữu đối tượng qua @StateObject. Thay đổi username hoặc isLoggedIn tự động vẽ lại ProfileView. @Published sử dụng Combine Publisher để thông báo cho SwiftUI về các thay đổi. Để truyền settings cho View con, sử dụng @ObservedObject:
Apple định nghĩa bốn cấp độ Data Flow trong SwiftUI: @State (cục bộ, value type), @Binding (hai chiều), @StateObject/@ObservedObject (reference type với ObservableObject), @EnvironmentObject (toàn cục, được tiêm qua môi trường). EnvironmentObject cho phép truyền dữ liệu qua toàn bộ hệ phân cấp View mà không cần truyền rõ ràng trong bộ khởi tạo. Ngoài ra, @AppStorage hoạt động với UserDefaults, @SceneStorage với trạng thái scene, @FetchRequest với Core Data. Việc chọn cấp độ Data Flow quyết định kiến trúc ứng dụng: màn hình đơn giản sử dụng State/Binding, mô-đun sử dụng ObservedObject, quy mô lớn sử dụng EnvironmentObject + giải pháp kiểu Redux (TCA, Composable Architecture).
| Property Wrapper | Quyền sở hữu | Kiểu | Khi nào sử dụng |
|---|---|---|---|
| @State | Cục bộ | Value (struct, enum) | Trạng thái đơn giản của một View (bộ đếm, nút bật/tắt, trường văn bản) |
| @Binding | Bên ngoài | Tham chiếu đến State | View con sửa đổi dữ liệu của cha |
| @StateObject | View sở hữu | Reference (class) | Nguồn chân lý cho mô hình dữ liệu phức tạp |
| @ObservedObject | Tiêm | Reference (class) | Mô hình được tạo bên ngoài View (truyền qua init) |
| @EnvironmentObject | Toàn cục | Reference (class) | Dữ liệu khả dụng cho toàn bộ hệ phân cấp (xác thực, chủ đề) |
Câu hỏi thường gặp
@State — dành cho value types (struct, enum, String, Int) và trạng thái cục bộ của một View. SwiftUI tự động quản lý bộ nhớ State. @StateObject — dành cho reference types (class) tuân theo ObservableObject. @StateObject sở hữu đối tượng và vẽ lại View khi các thuộc tính @Published thay đổi. Đối với bộ đếm đơn giản hãy dùng @State; đối với mô hình có logic nghiệp vụ hãy dùng @StateObject.
Có, SwiftUI tích hợp với UIKit qua UIHostingController (SwiftUI trong UIKit) và UIViewRepresentable (UIKit trong SwiftUI). UIHostingController bọc một SwiftUI View trong UIViewController. UIViewRepresentable cho phép sử dụng các thành phần UIKit (MKMapView, WKWebView) trong SwiftUI. Đây là cách tiếp cận tiêu chuẩn để di chuyển dự án từ UIKit sang SwiftUI.
ViewBuilder — một result builder (Swift 5.1) biến đổi tập hợp các View thành một giá trị duy nhất kiểu TupleView, Group hoặc ConditionalContent. ViewBuilder cho phép viết if/else và switch mệnh lệnh bên trong body khai báo. Không có ViewBuilder, bạn phải trả về AnyView hoặc Group cho mỗi khối có điều kiện. ViewBuilder là lý do tại sao body không cần dấu phẩy giữa các View.
Có, SwiftUI hỗ trợ iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ và visionOS 1+. Tuy nhiên, một số API chỉ khả dụng trên các phiên bản mới hơn: ví dụ, navigationStack (iOS 16+), Observable macro (iOS 17+). Để tương thích ngược, hãy sử dụng #available và các điều chỉnh UIKit.
Xcode Debug View Hierarchy hiển thị cây View SwiftUI với các bổ từ và khung. Công cụ SwiftUI Inspector (bảng điều khiển bên phải Xcode) cho phép sửa đổi bổ từ trong thời gian thực. self._printChanges() trong body ghi lại lý do vẽ lại. Instruments với mẫu SwiftUI theo dõi hiệu suất View và xác định các lần vẽ lại quá mức.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm