.onAppear คือตัวปรับแต่ง SwiftUI ที่ดำเนินการ closure เมื่อ View ถูกเพิ่มเข้าไปในลำดับชั้นอินเทอร์เฟซ การเรียกเกิดขึ้นหนึ่งครั้งต่อการปรากฏของอินสแตนซ์บนหน้าจอ และทำหน้าที่เป็นจุดหลักในการโหลดข้อมูล เริ่มต้นแอนิเมชัน และส่งเหตุการณ์วิเคราะห์ ตาม Apple Developer Documentation (2026) onAppear รับประกันการดำเนินการก่อนการเรนเดอร์ครั้งแรก แต่ไม่รับประกันการเรียกในการแสดงผลซ้ำแต่ละครั้งหาก View ยังคงอยู่ในหน่วยความจำ อ่านเพิ่มเติมเกี่ยวกับ SwiftUI ได้ในบทความ SwiftUI
ประเด็นสำคัญ
.onAppear คือตัวปรับแต่ง View ใน SwiftUI ที่รับ closure Void และดำเนินการเมื่อ View ปรากฏบนหน้าจอ ตัวปรับแต่งนี้เป็นส่วนหนึ่งของระบบวงจรชีวิตของคอมโพเนนต์ SwiftUI ร่วมกับ .onDisappear และ .task Apple แนะนำ onAppear พร้อมกับการเปิดตัว SwiftUI ใน iOS 13 และ watchOS 6 เพื่อแทนที่ viewDidLoad จาก UIKit
ในเชิงวากยสัมพันธ์ .onAppear ปรับแต่ง View ใด ๆ และส่งคืน View เดียวกันพร้อมการกระทำที่แนบมา ตัวประกอบ SwiftUI เรียก closure ที่ส่งผ่านหนึ่งครั้งเมื่อ view ถูกเพิ่มเข้าไปในลำดับชั้นและผ่านขั้นตอนการเรนเดอร์ หาก View ถูกนำออกแล้วเพิ่มอีกครั้ง (เช่น เมื่อเลื่อนในรายการ) onAppear จะถูกเรียกอีกครั้ง — พฤติกรรมนี้มักกลายเป็นแหล่งที่มาของข้อผิดพลาดที่ไม่คาดคิด
ไวยากรณ์พื้นฐานของตัวปรับแต่งค่อนข้างเรียบง่าย: onAppear โดยไม่มีพารามิเตอร์ SwiftUI ไม่มีวิธีในการส่งลำดับความสำคัญหรือแอนิเมชัน — closure ทำงานแบบซิงโครนัสบนเธรดหลักทันทีหลังจากการเรนเดอร์
struct ContentView: View {
var body: some View {
Text("Hello, SwiftUI!")
.onAppear {
print("View appeared on screen")
}
}
}
ข้อจำกัด: onAppear ไม่รองรับ async/await โดยตรง สำหรับการดำเนินการแบบอะซิงโครนัสภายใน closure จำเป็นต้องใช้ Task {} หรือฟังก์ชัน async/await แยกต่างหากที่เรียกผ่าน Task.detached ทำให้ onAppear สะดวกน้อยกว่าสำหรับคำขอเครือข่ายเมื่อเทียบกับตัวปรับแต่ง .task
.onAppear ถูกฝังอยู่ในไปป์ไลน์การเรนเดอร์ SwiftUI ในขั้นตอน layout+render เมื่อ SwiftUI คำนวณเนื้อหาของ View และตรวจพบการเปลี่ยนแปลงในลำดับชั้น มันจะเรียกใช้ callback onAppear สำหรับ view ที่เพิ่มใหม่ทั้งหมด ลำดับการเรียกเป็นไปตามการซ้อน: onAppear ของพาเรนต์ก่อน จากนั้นจึงองค์ประกอบย่อย
คุณสมบัติที่สำคัญของ SwiftUI คือ onAppear ไม่ได้ผูกติดกับการปรากฏบนหน้าจอจริง ตัวปรับแต่งจะถูกเรียกเมื่อ View ถูกเพิ่มเข้าไปในลำดับชั้นไม่ว่าผู้ใช้จะมองเห็นหรือไม่ (เช่น นอกหน้าจอใน ScrollView) สิ่งนี้แยกความแตกต่างของ SwiftUI จาก UIKit ที่ viewWillAppear ถูกเรียกเฉพาะเมื่อปรากฏจริงเท่านั้น
ลำดับการเรียกเป็นไปตามกฎพาเรนต์ก่อน: VStack หรือ NavigationView ได้รับ onAppear ก่อน จากนั้นแต่ละองค์ประกอบย่อยตามลำดับ สิ่งนี้สำคัญสำหรับการเริ่มต้นทรัพยากรที่ใช้ร่วมกัน: หากองค์ประกอบย่อยขึ้นอยู่กับข้อมูลที่โหลดโดยพาเรนต์ พวกเขาต้องตรวจสอบความพร้อมใช้งานผ่าน 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")
}
}
}
เอาต์พุตคอนโซล จะเป็น: Parent onAppear — ก่อน จากนั้น Child onAppear สองครั้งตามลำดับ พฤติกรรมนี้รับประกันโดย Apple และเสถียรในทุกเวอร์ชัน SwiftUI (iOS 13–18)
.onAppear มีหลายสถานการณ์การเรียกที่ขึ้นอยู่กับคอนเทนเนอร์และการนำทาง ใน NavigationStack onAppear ทำงานเมื่อ push คอนโทรลเลอร์ใหม่แต่ละครั้ง และเมื่อ pop — สำหรับคอนโทรลเลอร์ราก ใน TabView การเปลี่ยนแท็บจะเรียก onAppear สำหรับแท็บที่แสดงและ onDisappear สำหรับแท็บที่ซ่อน
ใน List และ ScrollView onAppear ถูกเรียกสำหรับเซลล์ที่เข้าสู่พื้นที่มองเห็นหรืออยู่ในบัฟเฟอร์การเรนเดอร์ล่วงหน้า iOS 18 ได้แนะนำกลไก prefetch ที่สามารถเรียก onAppear สำหรับเซลล์ที่อยู่ห่าง 2–3 หน้าจอก่อนการเลื่อน — สิ่งนี้ช่วยเพิ่มความเร็วในการรับรู้แต่อาจกระตุ้นคำขอเครือข่ายที่ไม่จำเป็น
NavigationStack (iOS 16+) จัดการสแต็กหน้าจอแตกต่างจาก NavigationView เมื่อ push หน้าจอใหม่ onAppear ทำงานเฉพาะบนหน้าจอใหม่ ในขณะที่หน้าจอปัจจุบันไม่ได้รับ onDisappear จนกว่าจะถูกลบจริง เมื่อ pop กระบวนการย้อนกลับจะเกิดขึ้น: onDisappear บนหน้าจอที่กำลังออก, onAppear บนหน้าจอที่กลับมา
| สถานการณ์ | onAppear | onDisappear |
|---|---|---|
| Push | หน้าจอใหม่ | ไม่ (หน้าจออยู่ในสแต็ก) |
| Pop | หน้าจอที่กลับมา | หน้าจอที่กำลังออก |
| เปลี่ยนแท็บ | แท็บใหม่ | แท็บเก่า |
| ปิด sheet | หน้าจอพาเรนต์ | sheet ที่เปิด |
การประยุกต์ใช้ในทางปฏิบัติ ของ onAppear ครอบคลุมสามหมวดหมู่หลัก: การโหลดข้อมูล การเริ่มต้นแอนิเมชัน และการส่งการวิเคราะห์ แต่ละสถานการณ์ต้องพิจารณาคุณสมบัติวงจรชีวิตของ SwiftUI เพื่อหลีกเลี่ยงการเรียกซ้ำซ้อนและการรั่วไหลของหน่วยความจำ
การโหลดข้อมูล เป็นกรณีการใช้งานที่พบบ่อยที่สุดของ onAppear ภายใน closure จะสร้าง Task สำหรับการเรียก async และผลลัพธ์จะถูกเก็บใน @State หรือ @StateObject สิ่งสำคัญคือต้องตรวจสอบว่าโหลดข้อมูลแล้วหรือไม่โดยใช้แฟล็ก isLoading หรือการตรวจสอบ 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()
}
}
}
}
การป้องกันการเรียกซ้ำ เป็นแนวปฏิบัติที่สำคัญ หาก SwiftUI สร้าง View ใหม่ (เช่น เมื่อหมุนหน้าจอ) onAppear จะทำงานอีกครั้งโดยไม่มีการป้องกัน ทางเลือกคือตัวปรับแต่ง .task ซึ่งยกเลิกคำขอก่อนหน้าโดยอัตโนมัติ
แอนิเมชันเข้า ใช้ onAppear เพื่อเปลี่ยนตัวแปรสถานะที่กระตุ้นแอนิเมชันผ่าน withAnimation หรือตัวปรับแต่ง animation รูปแบบทั่วไป: สถานะเริ่มต้น (opacity 0, offset 100), เปลี่ยนไปสู่สถานะสุดท้าย (opacity 1, offset 0) เมื่อปรากฏ
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
}
}
}
}
ความล่าช้า 0.3 วินาทีสร้างเอฟเฟกต์การปรากฏแบบต่อเนื่องหากมีหลายการ์ดบนหน้าจอ สำหรับรายการองค์ประกอบที่เคลื่อนไหว ให้ใช้ดัชนีองค์ประกอบเป็นตัวคูณความล่าช้า
.task คือตัวปรับแต่ง SwiftUI ที่เพิ่มใน iOS 15 ซึ่งแก้ปัญหาการดำเนินการแบบอะซิงโครนัสใน onAppear แตกต่างจาก onAppear ตรงที่ .task ยอมรับ closure แบบ async จัดการวงจรชีวิตโดยอัตโนมัติ และยกเลิกเมื่อ View หายไป ในขณะที่ onAppear ทำงานแบบซิงโครนัส .task จะเริ่มการดำเนินการแบบอะซิงโครนัสและให้ SwiftUI ยกเลิกเมื่อ onDisappear
ความแตกต่างหลักคือ การจัดการการยกเลิก เมื่อ .task สร้างการดำเนินการ async SwiftUI จะบันทึกการอ้างอิงไปยัง Task และเรียก cancel() โดยอัตโนมัติเมื่อ View ถูกลบออกจากลำดับชั้น onAppear ที่มี Task {} อยู่ภายในจะไม่ยกเลิกการดำเนินการที่กำลังทำงาน — มันยังคงทำงานต่อไปแม้หลังจาก View หายไป ซึ่งอาจทำให้เกิดสภาวะการแข่งขันหรือการเขียนในอินสแตนซ์ที่ถูกจัดสรรคืนแล้ว
| คุณลักษณะ | .onAppear | .task |
|---|---|---|
| เวอร์ชัน iOS | iOS 13+ | iOS 15+ |
| รองรับ async | ผ่าน Task {} เท่านั้น | async/await ดั้งเดิม |
| การยกเลิกอัตโนมัติ | ไม่ | เมื่อ View หายไป |
| การเรียกซ้ำ | ทุกครั้งที่ปรากฏ | ครั้งเดียวโดยค่าเริ่มต้น |
| โค้ดซิงโครนัส | ใช่ | เฉพาะ async |
การเลือกตัวปรับแต่ง: สำหรับการดำเนินการแบบซิงโครนัส (แอนิเมชัน, การวิเคราะห์, การบันทึก) ใช้ onAppear สำหรับการโหลดข้อมูลแบบอะซิงโครนัส (API, Core Data, ระบบไฟล์) ควรใช้ .task — ปลอดภัยกว่าและสะอาดกว่า
ข้อผิดพลาด 1: การเรียกหลายครั้งเนื่องจากการสร้าง View ใหม่ เมื่อ SwiftUI สร้างเนื้อหา View ใหม่ (การเปลี่ยนแปลงสถานะ, การหมุนหน้าจอ) onAppear อาจถูกเรียกอีกครั้ง วิธีแก้ไข — เพิ่มแฟล็กการโหลดหรือใช้ .equatable() เพื่อป้องกันการวาดซ้ำที่ไม่จำเป็น ตาม SwiftLee (2025) 40% ของข้อผิดพลาด SwiftUI ในโปรดักชันเกี่ยวข้องกับการเรียก onAppear ซ้ำ
ข้อผิดพลาด 2: หน่วยความจำรั่วไหลผ่านการอ้างอิงที่แข็งแกร่ง หาก closure ของ onAppear จับ self โดยไม่มีการอ้างอิงที่อ่อนแอ มันจะสร้าง retain cycle กับ View SwiftUI ไม่รับประกันการทำให้วัตถุที่ถูกจับเป็น null เมื่อ View หายไป ใช้ capture list [weak self] สำหรับ ViewModel หรือบริการ
ข้อผิดพลาด 3: การดำเนินการบนเธรดพื้นหลัง onAppear ทำงานบนเธรดหลัก — ซึ่งถูกต้องสำหรับการดำเนินการ UI แต่ถ้าคุณเริ่ม Task ภายใน onAppear ตรวจสอบให้แน่ใจว่าการอัปเดต @State เกิดขึ้นผ่าน MainActor.run Swift 5.9 ขึ้นไปจะกลับไปที่ MainActor โดยอัตโนมัติ แต่ควรระบุ @MainActor อย่างชัดเจน
รูปแบบที่มีแฟล็กการโหลดเป็นวิธีที่น่าเชื่อถือที่สุดในการป้องกันการซ้ำซ้อน เก็บแฟล็กใน @State หรือ @StateObject และรีเซ็ตเฉพาะเมื่ออัปเดตด้วยตนเอง ทางเลือกคือใช้ .task แทน onAppear: .task จะไม่เริ่มใหม่เมื่อวาดซ้ำโดยค่าเริ่มต้นหากการดำเนินการ async กำลังทำงานอยู่แล้ว
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()
}
}
}
}
คำถามที่พบบ่อย
viewDidLoad ถูกเรียกหนึ่งครั้งตลอดอายุของ UIViewController โดยไม่ขึ้นกับการมองเห็น .onAppear ถูกเรียกทุกครั้งที่ View ถูกเพิ่มเข้าไปในลำดับชั้น — หาก View ถูกลบและเพิ่มอีกครั้ง onAppear จะทำงานอีกครั้ง ใน NavigationView viewDidLoad ถูกเรียกในระหว่างการเริ่มต้น ในขณะที่ onAppear ถูกเรียกทุกครั้งที่แสดงหน้าจอ
ได้ ผ่าน wrapper Task { await asyncFunction() } อย่างไรก็ตาม สำหรับการดำเนินการ async ควรใช้ .task ซึ่งจัดการการยกเลิกโดยอัตโนมัติและไม่ต้องสร้าง Task ด้วยตนเอง .task ยังรับประกันการยกเลิกเมื่อ View หายไป ป้องกันการรั่วไหล
สาเหตุคือการสร้างเนื้อหา View ใหม่เนื่องจากการเปลี่ยนแปลงใน @State, @Published หรือการกำหนดค่าของบรรพบุรุษ SwiftUI อาจวาด View ใหม่เพื่อตอบสนองต่อการเปลี่ยนแปลงในคุณสมบัติที่สังเกตได้ใด ๆ นอกจากนี้ LazyVStack และ List เรียก onAppear สำหรับเซลล์ที่เข้าใกล้พื้นที่มองเห็น และอีกครั้งเมื่อเลื่อนขึ้นด้านบน
ใช่ .onAppear พร้อมใช้งานบนแพลตฟอร์ม SwiftUI ทั้งหมด: iOS 13+, watchOS 6+, tvOS 13+, macOS 10.15+ พฤติกรรมเหมือนกัน: ตัวปรับแต่งถูกเรียกเมื่อ View ถูกเพิ่มเข้าไปในลำดับชั้น บน watchOS onAppear ทำงานเมื่อแอปเปิดใช้งานจากสถานะสแตนด์บาย ซึ่งต้องพิจารณาในการออกแบบ
.onAppear ไม่รับพารามิเตอร์ — เฉพาะ closure Void ในการส่งพารามิเตอร์ ให้ใช้ closure ที่จับตัวแปรภายนอก วิธีทางเลือกคือสร้างตัวปรับแต่ง onAppear แบบกำหนดเองพร้อมพารามิเตอร์ผ่าน ViewModifier หรือสิ่งที่เทียบเท่ากับ .onChange
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม