List คือ View คอนเทนเนอร์ใน SwiftUI สำหรับแสดงข้อมูลเป็นรายการที่เลื่อนในแนวตั้งได้ คล้ายกับ UITableView ใน UIKit ตาม Apple Developer Documentation, 2024 List รองรับส่วนแบบคงที่และไดนามิก การปัดเพื่อดำเนินการ การเรียงลำดับแถวใหม่ และการดึงเพื่อรีเฟรช แตกต่างจาก UITableView ตรงที่ List ใช้ API เชิงประกาศที่อิงตาม SwiftUI และ ForEach จัดการการนำเซลล์กลับมาใช้ใหม่และประสิทธิภาพโดยอัตโนมัติเมื่อมีแถวจำนวนมาก
ประเด็นสำคัญ
List คือ View ที่แสดงลำดับของรายการในรายการที่เลื่อนในแนวตั้งได้ มันถูกนำมาใช้ใน iOS 13 พร้อมกับ SwiftUI และเป็นวิธีหลักในการแสดงรายการข้อมูล แทนที่ UITableView จาก UIKit List จัดการการนำเซลล์กลับมาใช้ใหม่ การเลื่อน และประสิทธิภาพโดยอัตโนมัติ
List ใช้การโหลดแบบขี้เกียจ: เซลล์จะถูกสร้างขึ้นเมื่อคุณเลื่อน ไม่ใช่ทั้งหมดในครั้งเดียว ซึ่งแตกต่างจาก VStack กับ ForEach ภายใน ScrollView ที่เซลล์ทั้งหมดถูกสร้างขึ้นในขณะเรนเดอร์ List ยังรองรับการปัดเพื่อดำเนินการ การดึงเพื่อรีเฟรช การแก้ไข (ลบ/ย้าย) และการเลือกแถวในตัว
ตาม Apple WWDC 2021 (Session 10072) List ใน iOS 15+ ได้รับการปรับปรุงประสิทธิภาพอย่างมีนัยสำคัญด้วยกลไกการเปรียบเทียบความแตกต่างใหม่ในระดับคอลเลกชัน ซึ่งทำให้ List มีประสิทธิภาพมากขึ้นเมื่ออัปเดตข้อมูล โดยเฉพาะสำหรับรายการที่มีแถวนับร้อย
นักพัฒนามักเลือกระหว่าง List และ ScrollView กับ VStack เพื่อแสดงชุดของ Views ความแตกต่างหลัก: List ใช้การนำเซลล์กลับมาใช้ใหม่ (เช่น UITableView) ในขณะที่ ScrollView + VStack สร้าง Views ทั้งหมดทันที สำหรับรายการขนาดคงที่ (สูงสุด 20 รายการ) ความแตกต่างนั้นเล็กน้อย สำหรับรายการไดนามิกที่มี 50+ แถว List เหมาะสมกว่าในแง่ประสิทธิภาพ
รายการแบบคงที่ คือรายการที่มีจำนวนแถวคงที่ซึ่งระบุโดยตรงในเนื้อหาของ List ใช้สำหรับเมนู การตั้งค่า และฟอร์มที่มีชุดรายการที่ทราบ แต่ละแถวถูกประกาศอย่างชัดเจน โดยไม่มีลูปหรือ ForEach
// รายการแบบคงที่ (สำหรับเมนูและการตั้งค่า)
List {
Text("โปรไฟล์")
Text("การตั้งค่า")
Text("เกี่ยวกับ")
}
// รายการแบบไดนามิก (สำหรับข้อมูล)
struct UserList: View {
let users: [User]
var body: some View {
List(users) { user in
HStack {
Text(user.name)
Text(user.role)
.foregroundColor(.secondary)
}
}
}
}
รายการแบบไดนามิก ใช้ตัวเริ่มต้น List(data:rowContent:) หรือ ForEach ภายในเนื้อหาของ List วิธีแรกสะดวกเมื่อแต่ละแถวสอดคล้องกับหนึ่งรายการข้อมูล วิธีที่สองมีประโยชน์เมื่อมีส่วนหรือรายการเพิ่มเติมระหว่างข้อมูล
การระบุ (Identifiable): สำหรับรายการไดนามิก รายการข้อมูลต้องเป็นไปตามโปรโตคอล Identifiable หรือคุณต้องระบุ KeyPath ไปยังตัวระบุที่ไม่ซ้ำกันใน tuple data:id SwiftUI ใช้ตัวระบุเพื่อติดตามการเปลี่ยนแปลง: การเพิ่ม การลบ และการย้ายแถว
Section คือ View สำหรับจัดกลุ่มแถวใน List ด้วยหัวเรื่องและส่วนท้ายที่เป็นตัวเลือก Section ยอมรับ header และ footer เป็น ViewBuilder ทำให้สามารถใช้ไม่เพียงแค่ข้อความ แต่ยังใช้ Views แบบกำหนดเองสำหรับหัวเรื่องส่วนได้
struct SettingsView: View {
var body: some View {
List {
Section(header: Text("บัญชี")) {
Text("ชื่อ")
Text("อีเมล")
}
Section(header: Text("การแจ้งเตือน")) {
Toggle("Push", isOn: $pushEnabled)
Toggle("Email", isOn: $emailEnabled)
}
}
.listStyle(.insetGrouped)
}
}
// ส่วนไดนามิกด้วย ForEach
List {
ForEach(groupedData.keys.sorted(), id: \.self) { key in
Section(header: Text(key)) {
ForEach(groupedData[key]!) { item in
Text(item.title)
}
}
}
}
สไตล์ของ List: SwiftUI มีสไตล์ในตัวหลายแบบผ่านตัวปรับแต่ง .listStyle() .insetGrouped — มาตรฐานสำหรับ iOS Settings, .plain — มินิมอล, .inset — มีการเยื้อง, .sidebar — สำหรับ Sidebar บน iPad
ตาม SwiftUI Cookbook (2024) Section ที่มีส่วนไดนามิกและ ForEvaluate ภายในเป็นรูปแบบมาตรฐานสำหรับการจัดกลุ่มข้อมูลในแอปพลิเคชันที่มีโครงสร้างซับซ้อน กฎสำคัญ: อย่าวาง Section ซ้อนภายใน Section และอย่าใช้ตัวเริ่มต้น List(data:) ร่วมกับ Section — ให้ใช้ ForEach ภายในเนื้อหาของ List
.swipeActions(edge:allowsFullSwipe:content:) — ตัวปรับแต่งสำหรับ iOS 15+ ที่เพิ่มการปัดเพื่อดำเนินการให้กับแถวของ List ช่วยให้แสดงปุ่มเมื่อปัดไปทางซ้าย (ค่าเริ่มต้น) หรือขวา พร้อมสีและบทบาทที่แตกต่างกัน (destructive, cancel)
struct TaskList: View {
@Binding var tasks: [Task]
var body: some View {
List {
ForEach($tasks) { $task in
Text(task.title)
.swipeActions(edge: .trailing) {
Button("ลบ", role: .destructive) {
tasks.removeAll { $0.id == task.id }
}
}
.swipeActions(edge: .leading) {
Button(task.isDone ? "เลิกทำ" : "เสร็จสิ้น") {
task.isDone.toggle()
}
.tint(.green)
}
}
}
.refreshable {
// การโหลดข้อมูลแบบอะซิงโครนัส
await loadTasks()
}
}
}
.refreshable — ตัวปรับแต่งสำหรับ iOS 15+ ที่เพิ่มการดึงเพื่อรีเฟรช ยอมรับคลosureแบบอะซิงโครนัสที่จะทำงานเมื่อผู้ใช้ดึงรายการลงมา SwiftUI จะแสดงตัวบ่งชี้การโหลดโดยอัตโนมัติ หลังจากการดำเนินการเสร็จสิ้น ตัวบ่งชี้จะถูกซ่อน
.onDelete และ .onMove — ตัวปรับแต่งสำหรับ iOS 13+ ที่เพิ่มการรองรับการลบและการเรียงลำดับแถวใหม่ ในการใช้งาน ให้ห่อข้อมูลใน ForEach ด้วย Binding หรือส่งคลosureผ่าน .onDelete(perform:) บน List หรือ ForEach
ประสิทธิภาพของ List ขึ้นอยู่กับจำนวนแถว ความซับซ้อนของแต่ละเซลล์ และความถี่ในการอัปเดตข้อมูล SwiftUI ใช้การโหลดแบบขี้เกียจและการนำเซลล์กลับมาใช้ใหม่ (คล้ายกับ UITableView.dequeueReusableCell) แต่อาจจำเป็นต้องปรับให้เหมาะสมเพิ่มเติมสำหรับรายการที่มี 500+ แถว
| การปรับให้เหมาะสม | คำอธิบาย | เวอร์ชัน iOS |
|---|---|---|
| Identifiable | ID ที่ไม่ซ้ำกันสำหรับแต่ละรายการ | iOS 13+ |
| EquatableView | หลีกเลี่ยงการวาดใหม่เมื่อข้อมูลเท่ากัน | iOS 13+ |
| id(_:) | บังคับสร้าง View ใหม่เมื่อ ID เปลี่ยนแปลง | iOS 13+ |
| .equatable() | การเปรียบเทียบอย่างเคร่งครัดตาม Equatable | iOS 15+ |
| Diffable data | การเปรียบเทียบความแตกต่างอัตโนมัติเมื่อมีการเปลี่ยนแปลง | iOS 15+ |
ปัญหา 1: การอัปเดตบ่อยครั้ง หากข้อมูลในรายการได้รับการอัปเดตบ่อยครั้ง (เช่น ทุกวินาที) List อาจวาดเซลล์ที่มองเห็นใหม่ทุกครั้งที่สถานะเปลี่ยนแปลง วิธีแก้ไข: ใช้โครงสร้าง (value types) สำหรับข้อมูล — SwiftUI เปรียบเทียบตามค่าและวาดเฉพาะแถวที่เปลี่ยนแปลงใหม่
ปัญหา 2: เซลล์ที่มีน้ำหนักมาก หากแต่ละแถวมีลำดับชั้น View ที่ซับซ้อน รูปภาพ และแอนิเมชัน การเลื่อนอาจช้าลง วิธีแก้ไข: แยกเซลล์ออกเป็น Views แยกต่างหาก ใช้ EquatableView เพื่อป้องกันการวาดใหม่ที่ไม่จำเป็น ตาม SwiftUI Lab (2024) การแบ่งแถวที่ซับซ้อนออกเป็นส่วนประกอบย่อยช่วยลดเวลาในการเรนเดอร์ลง 30–50%
ปัญหา 3: จำนวนแถวมาก ด้วย 1000+ แถว List ยังคงทำงานได้อย่างมีประสิทธิภาพด้วยการโหลดแบบขี้เกียจ แต่การโหลดเริ่มต้นอาจช้าลงเนื่องจากการคำนวณเค้าโครง วิธีแก้ไข: ใช้ LazyVStack เฉพาะสำหรับรายการที่มีแถวเหมือนกันซึ่งไม่จำเป็นต้องใช้คุณสมบัติของ List (การปัด ส่วน) สำหรับรายการที่มีคุณสมบัติครบถ้วน List ยังคงเป็นตัวเลือกที่ดีที่สุด
คำถามที่พบบ่อย
List คือ View คอนเทนเนอร์สำหรับแสดงรายการข้อมูลที่เลื่อนได้ใน SwiftUI เทียบเท่ากับ UITableView ใน UIKit พร้อม API เชิงประกาศ รองรับส่วน การปัดเพื่อดำเนินการ การดึงเพื่อรีเฟรช การแก้ไข และการปรับแต่งผ่าน .listStyle()
List ใช้ การโหลดแบบขี้เกียจและการนำเซลล์กลับมาใช้ใหม่ — เซลล์จะถูกสร้างขึ้นเมื่อคุณเลื่อน ScrollView + VStack สร้าง Views ทั้งหมดในครั้งเดียว สำหรับรายการที่มี 50+ แถว List เหมาะสมกว่า สำหรับชุดขนาดเล็กคงที่ (สูงสุด 20 รายการ) ความแตกต่างนั้นเล็กน้อย
ใช้ตัวปรับแต่ง .refreshable (iOS 15+) ส่งคลosureแบบอะซิงโครนัสพร้อมตรรกะการอัปเดตข้อมูล SwiftUI จะแสดงตัวบ่งชี้การโหลดโดยอัตโนมัติและซ่อนหลังจากดำเนินการแบบอะซิงโครนัสเสร็จสิ้น
ใช้ Section View พร้อมหัวเรื่องและส่วนท้ายที่เป็นตัวเลือก วางแถวรายการภายใน Section สำหรับส่วนไดนามิก ให้ใช้ ForEach กับ groupedData สไตล์รายการถูกกำหนดค่าผ่าน .listStyle(.insetGrouped) เพื่อให้มีลักษณะคล้าย iOS
ใช้ โครงสร้าง (value types) สำหรับข้อมูล แยกเซลล์ที่ซับซ้อนออกเป็น Views แยกต่างหากด้วย EquatableView หลีกเลี่ยงการอัปเดตสถานะบ่อยครั้งในแต่ละแถว สำหรับรายการที่มี 1000+ แถว ให้พิจารณา LazyVStack หากไม่จำเป็นต้องใช้คุณสมบัติของ List
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม