.onAppear là bộ điều chỉnh SwiftUI thực thi một closure khi View được thêm vào hệ thống phân cấp giao diện. Lời gọi xảy ra một lần cho mỗi lần xuất hiện của thể hiện trên màn hình và đóng vai trò là điểm chính để tải dữ liệu, khởi chạy hoạt ảnh và gửi sự kiện phân tích. Theo Apple Developer Documentation (2026), onAppear đảm bảo thực thi trước lần render đầu tiên, nhưng không đảm bảo lời gọi ở mỗi lần hiển thị lặp lại nếu View vẫn còn trong bộ nhớ. Đọc thêm về SwiftUI trong bài viết về SwiftUI.
Những điểm chính
.onAppear là bộ điều chỉnh View trong SwiftUI nhận một closure Void và thực thi nó khi View trở nên hiển thị trên màn hình. Bộ điều chỉnh này là một phần của hệ thống vòng đời thành phần SwiftUI cùng với .onDisappear và .task. Apple giới thiệu onAppear cùng với việc phát hành SwiftUI trong iOS 13 và watchOS 6 như một sự thay thế cho viewDidLoad từ UIKit.
Về mặt cú pháp, .onAppear sửa đổi bất kỳ View nào và trả về cùng View đó với một hành động được đính kèm. Trình soạn SwiftUI gọi closure được truyền một lần khi view được thêm vào hệ thống phân cấp và vượt qua giai đoạn render. Nếu một View bị xóa và sau đó được thêm lại (ví dụ: khi cuộn trong danh sách), onAppear được gọi lại — hành vi này thường trở thành nguồn gốc của các lỗi bất ngờ.
Cú pháp cơ bản của bộ điều chỉnh rất tối thiểu: onAppear không có tham số. SwiftUI không cung cấp cách để truyền ưu tiên hoặc hoạt ảnh — closure thực thi đồng bộ trên luồng chính ngay sau khi render.
struct ContentView: View {
var body: some View {
Text("Hello, SwiftUI!")
.onAppear {
print("View appeared on screen")
}
}
}
Giới hạn: onAppear không hỗ trợ async/await trực tiếp. Đối với các thao tác không đồng bộ bên trong closure, cần có Task {} hoặc một hàm async/await riêng được gọi qua Task.detached. Điều này làm cho onAppear kém tiện lợi hơn cho các yêu cầu mạng so với bộ điều chỉnh .task.
.onAppear được nhúng vào đường ống render của SwiftUI ở giai đoạn layout+render. Khi SwiftUI tính toán phần thân của View và phát hiện sự thay đổi trong hệ thống phân cấp, nó kích hoạt các callback onAppear cho tất cả các view mới được thêm vào. Thứ tự gọi tuân theo sự lồng nhau: onAppear của cha trước, sau đó đến các phần tử con.
Một đặc điểm quan trọng của SwiftUI là onAppear không gắn với sự xuất hiện vật lý trên màn hình. Bộ điều chỉnh được gọi khi một View được thêm vào hệ thống phân cấp bất kể nó có hiển thị với người dùng hay không (ví dụ: ngoài màn hình trong ScrollView). Điều này phân biệt SwiftUI với UIKit, nơi viewWillAppear chỉ kích hoạt khi xuất hiện thực tế.
Thứ tự gọi tuân theo quy tắc cha-trước: VStack hoặc NavigationView nhận onAppear trước, sau đó từng phần tử con theo thứ tự. Điều này rất quan trọng cho việc khởi tạo tài nguyên dùng chung: nếu các phần tử con phụ thuộc vào dữ liệu do cha tải, chúng phải kiểm tra tính khả dụng qua Optional.
struct ParentView: View {
var body: some View {
VStack {
ChildView()
ChildView()
}
.onAppear {
print("Parent onAppear — first")
}
}
}
struct ChildView: View {
var body: some View {
Text("Child")
.onAppear {
print("Child onAppear")
}
}
}
Đầu ra console sẽ là: Parent onAppear — đầu tiên, sau đó Child onAppear hai lần theo thứ tự. Hành vi này được Apple đảm bảo và ổn định trên tất cả các phiên bản SwiftUI (iOS 13–18).
.onAppear có một số kịch bản gọi phụ thuộc vào container và điều hướng. Trong NavigationStack, onAppear kích hoạt ở mỗi lần push một bộ điều khiển mới và ở pop — cho bộ điều khiển gốc. Trong TabView, chuyển đổi tab gọi onAppear cho tab đang hiển thị và onDisappear cho tab bị ẩn.
Trong List và ScrollView, onAppear được gọi cho các ô đã vào vùng hiển thị hoặc đang trong bộ đệm render trước. iOS 18 đã giới thiệu cơ chế prefetch có thể gọi onAppear cho các ô cách 2–3 màn hình trước khi cuộn — điều này tăng tốc độ nhận thức nhưng có thể kích hoạt các yêu cầu mạng không cần thiết.
NavigationStack (iOS 16+) quản lý ngăn xếp màn hình khác với NavigationView. Khi push một màn hình mới, onAppear chỉ kích hoạt trên màn hình mới, trong khi màn hình hiện tại không nhận onDisappear cho đến khi bị xóa thực sự. Khi pop, quá trình ngược lại xảy ra: onDisappear trên màn hình rời đi, onAppear trên màn hình quay lại.
| Kịch bản | onAppear | onDisappear |
|---|---|---|
| Push | Màn hình mới | Không (màn hình ở lại trong ngăn xếp) |
| Pop | Màn hình quay lại | Màn hình rời đi |
| Chuyển tab | Tab mới | Tab cũ |
| Đóng sheet | Màn hình cha | Sheet đã mở |
Ứng dụng thực tế của onAppear bao gồm ba loại chính: tải dữ liệu, khởi chạy hoạt ảnh và gửi phân tích. Mỗi kịch bản đòi hỏi phải xem xét các đặc điểm vòng đời của SwiftUI để tránh các lời gọi trùng lặp và rò rỉ bộ nhớ.
Tải dữ liệu là trường hợp sử dụng phổ biến nhất của onAppear. Bên trong closure, một Task được tạo cho lời gọi async và kết quả được lưu trữ trong @State hoặc @StateObject. Điều quan trọng là kiểm tra xem dữ liệu đã được tải chưa bằng cách sử dụng cờ isLoading hoặc kiểm tra nil.
struct ProfileView: View {
@StateObject private var viewModel = ProfileViewModel()
var body: some View {
VStack {
if viewModel.isLoading {
ProgressView()
} else {
Text(viewModel.userName)
}
}
.onAppear {
guard viewModel.userName == nil else { return }
Task {
await viewModel.loadProfile()
}
}
}
}
Bảo vệ chống tải lại là một thực hành quan trọng. Nếu SwiftUI tạo lại View (ví dụ: khi xoay màn hình), onAppear sẽ kích hoạt lại mà không có bảo vệ. Một giải pháp thay thế là bộ điều chỉnh .task, tự động hủy yêu cầu trước đó.
Hoạt ảnh đầu vào sử dụng onAppear để thay đổi các biến trạng thái kích hoạt hoạt ảnh qua withAnimation hoặc bộ điều chỉnh animation. Mẫu điển hình: trạng thái ban đầu (opacity 0, offset 100), chuyển tiếp sang trạng thái cuối (opacity 1, offset 0) khi xuất hiện.
struct AnimatedCard: View {
@State private var isVisible = false
var body: some View {
RoundedRectangle(cornerRadius: 12)
.fill(Color.blue)
.opacity(isVisible ? 1 : 0)
.offset(y: isVisible ? 0 : 50)
.animation(.spring(), value: isVisible)
.onAppear {
withAnimation(.spring().delay(0.3)) {
isVisible = true
}
}
}
}
Độ trễ 0,3 giây tạo hiệu ứng xuất hiện tuần tự nếu có nhiều thẻ trên màn hình. Đối với danh sách các phần tử được tạo hoạt ảnh, hãy sử dụng chỉ mục phần tử làm hệ số nhân độ trễ.
.task là bộ điều chỉnh SwiftUI được thêm vào trong iOS 15 giải quyết vấn đề về các thao tác không đồng bộ trong onAppear. Không giống như onAppear, .task chấp nhận một closure async, tự động quản lý vòng đời của nó và hủy nó khi View biến mất. Trong khi onAppear thực thi đồng bộ, .task khởi chạy một thao tác không đồng bộ và cho phép SwiftUI hủy nó tại onDisappear.
Sự khác biệt chính là quản lý hủy bỏ. Khi .task tạo một thao tác async, SwiftUI lưu một tham chiếu đến Task và tự động gọi cancel() khi View bị xóa khỏi hệ thống phân cấp. onAppear với Task {} bên trong không hủy thao tác đang chạy — nó tiếp tục thực thi ngay cả sau khi View đã biến mất, điều này có thể gây ra điều kiện tranh chấp hoặc ghi vào một thể hiện đã được giải phóng.
| Đặc tính | .onAppear | .task |
|---|---|---|
| Phiên bản iOS | iOS 13+ | iOS 15+ |
| Hỗ trợ async | Chỉ qua Task {} | Async/await gốc |
| Tự động hủy | Không | Khi View biến mất |
| Gọi lại | Mỗi lần xuất hiện | Một lần theo mặc định |
| Mã đồng bộ | Có | Chỉ async |
Lựa chọn bộ điều chỉnh: cho các hành động đồng bộ (hoạt ảnh, phân tích, ghi nhật ký) hãy sử dụng onAppear. Cho tải dữ liệu không đồng bộ (API, Core Data, hệ thống tệp) hãy ưu tiên .task — nó an toàn hơn và sạch hơn.
Lỗi 1: nhiều lần gọi do View bị tạo lại. Khi SwiftUI tạo lại phần thân View (thay đổi trạng thái, xoay màn hình), onAppear có thể được gọi lại. Giải pháp — thêm cờ tải hoặc sử dụng .equatable() để ngăn chặn việc vẽ lại không cần thiết. Theo SwiftLee (2025), 40% lỗi SwiftUI trong sản xuất có liên quan đến các lời gọi onAppear lặp lại.
Lỗi 2: rò rỉ bộ nhớ qua tham chiếu mạnh. Nếu closure onAppear capture self mà không có tham chiếu yếu, nó tạo ra một chu kỳ giữ lại với View. SwiftUI không đảm bảo việc hủy các đối tượng bị capture khi View biến mất. Sử dụng danh sách capture [weak self] cho ViewModel hoặc dịch vụ.
Lỗi 3: thực thi trên luồng nền. onAppear thực thi trên luồng chính — điều này đúng cho các thao tác UI. Nhưng nếu bạn khởi chạy một Task bên trong onAppear, hãy đảm bảo rằng việc cập nhật @State diễn ra qua MainActor.run. Swift 5.9 trở lên tự động quay lại MainActor, nhưng tốt hơn nên chỉ định @MainActor một cách rõ ràng.
Mẫu với cờ tải là cách đáng tin cậy nhất để bảo vệ khỏi sự trùng lặp. Lưu trữ cờ trong @State hoặc @StateObject và chỉ đặt lại khi cập nhật thủ công. Một giải pháp thay thế là sử dụng .task thay vì onAppear: .task không khởi động lại khi vẽ lại theo mặc định nếu thao tác async đã đang chạy.
struct SafeView: View {
@State private var hasAppeared = false
@State private var items: [Item] = []
var body: some View {
List(items, id: \.id) { item in
Text(item.name)
}
.onAppear {
guard !hasAppeared else { return }
hasAppeared = true
Task {
items = await DataService.shared.fetchItems()
}
}
}
}
Câu hỏi thường gặp
viewDidLoad được gọi một lần trong suốt vòng đời của UIViewController, bất kể khả năng hiển thị. .onAppear được gọi mỗi khi một View được thêm vào hệ thống phân cấp — nếu một View bị xóa và được thêm lại, onAppear kích hoạt lại. Trong NavigationView, viewDidLoad được gọi trong quá trình khởi tạo, trong khi onAppear được gọi ở mỗi lần hiển thị màn hình.
Có, thông qua một wrapper Task { await asyncFunction() }. Tuy nhiên, cho các thao tác async, .task được ưu tiên hơn vì nó tự động quản lý việc hủy bỏ và không yêu cầu tạo Task thủ công. .task cũng đảm bảo hủy bỏ khi View biến mất, ngăn ngừa rò rỉ.
Nguyên nhân là việc tạo lại phần thân View do thay đổi trong @State, @Published hoặc cấu hình của tổ tiên. SwiftUI có thể vẽ lại View để phản ứng với các thay đổi trong bất kỳ thuộc tính quan sát được nào. Ngoài ra, LazyVStack và List gọi onAppear cho các ô đang tiến gần đến vùng hiển thị và lại gọi khi cuộn lên trên.
Có, .onAppear có sẵn trên tất cả các nền tảng SwiftUI: iOS 13+, watchOS 6+, tvOS 13+, macOS 10.15+. Hành vi giống hệt nhau: bộ điều chỉnh được gọi khi một View được thêm vào hệ thống phân cấp. Trên watchOS, onAppear kích hoạt khi ứng dụng được kích hoạt từ trạng thái chờ, điều này cần được xem xét trong thiết kế.
.onAppear không chấp nhận tham số — chỉ một closure Void. Để truyền tham số, hãy sử dụng một closure capture các biến bên ngoài. Một cách tiếp cận thay thế là tạo một bộ điều chỉnh onAppear tùy chỉnh với tham số thông qua ViewModifier hoặc một tương đương của .onChange.
Tóm tắ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