.onAppear: nguyên lý hoạt động, vòng đời và ví dụ trong SwiftUI

Tác giả: IT Sectr Đã đăng: 2026-06-26 Thời gian đọc: 10 phút

.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 SwiftUI để thực thi mã khi View xuất hiện trên màn hình.
  • Thực thi một lần — onAppear được gọi một lần cho mỗi vòng đời View nếu nó ở lại trong bộ nhớ.
  • Tải dữ liệu — trường hợp sử dụng chính của onAppear: fetch từ API, đọc từ Core Data hoặc UserDefaults.
  • Hoạt ảnh — onAppear khởi chạy hoạt ảnh đầu vào: opacity, scale, offset với độ trễ.
  • Phân tích — các sự kiện screen view, impression, page open được gửi qua onAppear.

.onAppear là gì?

.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 onAppear

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.

swift
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 hoạt động như thế nào trong vòng đời View

.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 onAppear

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.

swift
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).

Khi nào .onAppear được gọi

.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 ListScrollView, 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.

Hành vi gọi trong NavigationStack

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ảnonAppearonDisappear
PushMàn hình mớiKhông (màn hình ở lại trong ngăn xếp)
PopMàn hình quay lạiMàn hình rời đi
Chuyển tabTab mớiTab cũ
Đóng sheetMàn hình chaSheet đã mở

Ví dụ sử dụng .onAppear

Ứ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 từ API

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.

swift
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 đó.

Khởi chạy hoạt ảnh đầu vào

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.

swift
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ễ.

.onAppear vs .task — sự khác biệt

.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 iOSiOS 13+iOS 15+
Hỗ trợ asyncChỉ qua Task {}Async/await gốc
Tự động hủyKhôngKhi View biến mất
Gọi lạiMỗi lần xuất hiệnMột lần theo mặc định
Mã đồng bộ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 thường gặp với .onAppear

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.

Cách tránh các lời gọi lặp lại

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.

swift
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

.onAppear khác gì so với viewDidLoad trong UIKit?

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ể gọi một hàm async bên trong .onAppear không?

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ỉ.

Tại sao .onAppear được gọi nhiều lần?

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.

.onAppear có hoạt động trên watchOS và tvOS không?

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ế.

Làm thế nào để truyền tham số cho .onAppear?

.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

  • .onAppear là bộ điều chỉnh SwiftUI để thực thi mã khi một View được thêm vào hệ thống phân cấp giao diện.
  • Gọi một lần — onAppear được gọi một lần cho mỗi thể hiện View nếu nó ở lại trong bộ nhớ.
  • Thứ tự cha trước — View cha nhận onAppear trước View con.
  • Trường hợp sử dụng chính — tải dữ liệu, khởi chạy hoạt ảnh, gửi phân tích.
  • .task được ưu tiên cho các thao tác async nhờ tự động hủy khi View biến mất.
  • Kiểm tra bảo vệ — bắt buộc để tránh các lời gọi lặp lại khi vẽ lại View.

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.

Thảo luận dự án

Đọc thêm