UITableView: พื้นฐาน รายการและเซลล์ในแอป iOS

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-02-23 เวลาอ่าน: 9 นาที

UITableView — คอมโพเนนต์หลักของ UIKit สำหรับแสดงรายการแนวตั้งใน iOS แต่ละแถวของตารางคือเซลล์ UITableViewCell ที่มีสไตล์ที่กำหนดไว้ล่วงหน้า เราอธิบายพื้นฐานของ UITableView: DataSource, delegate, การใช้เซลล์ซ้ำ และประสิทธิภาพของตารางในแอปพลิเคชัน iOS ตามข้อมูลของ Apple (Human Interface Guidelines, 2026) UITableView ยังคงเป็นคอมโพเนนต์ UI ที่ใช้มากที่สุดใน iOS — 85% ของแอปใน 100 อันดับแรกของ App Store มีตารางอย่างน้อยหนึ่งตาราง สำหรับเลย์เอาต์ที่ซับซ้อน อ่าน บทความเกี่ยวกับ UICollectionView

ประเด็นสำคัญ

  • UITableView — คอมโพเนนต์ UIKit สำหรับรายการแนวตั้ง ใช้ได้ตั้งแต่ iOS 2.0 (iPhone OS 2)
  • UITableViewCell — เซลล์ตารางที่มีสไตล์ในตัวสี่แบบและตัวเลือกการปรับแต่ง
  • UITableViewDataSource — โปรโตคอลที่กำหนดจำนวนแถวและเซลล์สำหรับแต่ละส่วน
  • UITableViewDelegate — โปรโตคอลสำหรับจัดการการเลือก การแก้ไข และการกำหนดความสูงของแถว
  • UITableViewDiffableDataSource — DataSource ที่ทันสมัย (iOS 13+) พร้อมการคำนวณการเปลี่ยนแปลงอัตโนมัติ

UITableView ใน iOS คืออะไร

UITableView เป็นคลาสจากเฟรมเวิร์ก UIKit ที่ออกแบบมาเพื่อแสดงรายการแถวที่เลื่อนได้ในคอลัมน์เดียว แต่ละแถวแสดงโดยออบเจกต์ UITableViewCell UITableView ปรากฏครั้งแรกใน iPhone OS 2 (2008) และตั้งแต่นั้นมาก็ยังคงเป็นคอมโพเนนต์หลักสำหรับรายการใน iOS สถาปัตยกรรมของ UITableView สร้างขึ้นบนรูปแบบ MVC: ข้อมูลถูกจัดการโดย DataSource ลักษณะและพฤติกรรมโดย Delegate และการนำเสนอโดยตารางเอง

สองสไตล์ตาราง: .plain (รายการต่อเนื่องพร้อมส่วนเสริม) และ .grouped (ส่วนที่จัดกลุ่มพร้อมมุมมนและระยะขอบ) iOS 13 เพิ่ม .insetGrouped — สไตล์ grouped ที่มีระยะขอบจากขอบ ใช้ในแอปการตั้งค่าและสุขภาพ การเลือกสไตล์ส่งผลต่อลักษณะเริ่มต้น: ตารางแบบ plain จะยึดส่วนหัวของส่วนไว้ด้านบนเมื่อเลื่อน (sticky header) ส่วนแบบ grouped จะไม่ยึด

เซลล์ปรับขนาดอัตโนมัติ

ตั้งแต่ iOS 8 เป็นต้นไป UITableView รองรับเซลล์ปรับขนาดอัตโนมัติ (self-sizing cells) หากต้องการเปิดใช้งาน ให้ตั้งค่า tableView.estimatedRowHeight (เช่น 80) และ tableView.rowHeight = UITableView.automaticDimension ตารางจะคำนวณความสูงของแถวโดยอัตโนมัติตามข้อจำกัดของ Auto Layout ภายในเซลล์ การปรับขนาดอัตโนมัติจะลดประสิทธิภาพสำหรับเซลล์ที่ซับซ้อน — ใช้ความสูงคงที่ (rowHeight) สำหรับตารางที่มีมากกว่า 500 รายการ

สไตล์ UITableViewCell: Basic, Subtitle, Value1, Value2

UITableViewCell มีสไตล์ในตัวสี่แบบที่ครอบคลุม 80% ของสถานการณ์โดยไม่จำเป็นต้องสร้างเซลล์ที่กำหนดเอง แต่ละสไตล์ประกอบด้วย textLabel, detailTextLabel และ imageView สไตล์จะถูกเลือกเมื่อเริ่มต้นเซลล์ผ่าน init(style:reuseIdentifier:)

สไตล์textLabeldetailTextLabelimageViewตัวอย่าง
.default (.basic)ซ้าย ตัวหนาไม่มีไม่บังคับเมนู รายการ
.subtitleซ้าย ตัวหนาใต้ textLabel สีเทาไม่บังคับรายชื่อ เพลย์ลิสต์
.value1ซ้าย ตัวหนาขวา สีเทาไม่มีการตั้งค่าพร้อมค่า
.value2ขวา สีน้ำเงินซ้าย สีเทาไม่มีสมุดโทรศัพท์ (สไตล์ iOS 6)
swift
// การสร้างตารางด้วยเซลล์ที่กำหนดเอง
class ContactListController: UIViewController {

    private let tableView = UITableView(frame: .zero, style: .insetGrouped)

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.dataSource = self
        tableView.delegate = self
        tableView.rowHeight = 60
        view.addSubview(tableView)

        // Auto Layout
        tableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

extension ContactListController: UITableViewDataSource {

    func tableView(_ tableView: UITableView,
                         numberOfRowsInSection section: Int) -> Int {
        contacts.count
    }

    func tableView(_ tableView: UITableView,
                         cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        let contact = contacts[indexPath.row]

        var content = cell.defaultContentConfiguration()
        content.text = contact.name
        content.secondaryText = contact.phone
        content.image = UIImage(systemName: "person.circle")
        cell.contentConfiguration = content

        return cell
    }
}

UIContentConfiguration (iOS 14+) — วิธีที่ทันสมัยในการกำหนดค่าเซลล์ผ่าน contentConfiguration แทนการเข้าถึง textLabel/detailTextLabel โดยตรง ใช้ cell.defaultContentConfiguration() หรือสร้าง UIContentConfiguration ที่กำหนดเอง ข้อดี: รองรับ Dynamic Type โหมดมืด และ VoiceOver ในตัว Apple (WWDC 2024) แนะนำ contentConfiguration สำหรับ UITableView ใหม่ทั้งหมด

DataSource และ Delegate: วิธีการที่จำเป็น

UITableViewDataSource — โปรโตคอลที่ให้ข้อมูลสำหรับตาราง วิธีการที่จำเป็น: tableView(_:numberOfRowsInSection:) และ tableView(_:cellForRowAt:) หากไม่มีวิธีการเหล่านี้ ตารางจะไม่สามารถแสดงแถวใดได้เลย UITableViewDelegate — โปรโตคอลสำหรับจัดการลักษณะและพฤติกรรม: ความสูงของแถว การเลือก การแก้ไข การดำเนินการตามบริบท

swift
// ตัวอย่างที่มีหลายส่วนและการแก้ไข
extension ContactListController: UITableViewDelegate {

    // ความสูงที่กำหนดเองสำหรับแต่ละแถว
    func tableView(_ tableView: UITableView,
                         heightForRowAt indexPath: IndexPath) -> CGFloat {
        80
    }

    // การจัดการการเลือก
    func tableView(_ tableView: UITableView,
                         didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let contact = contacts[indexPath.row]
        showDetail(for: contact)
    }

    // Swipe-to-delete
    func tableView(_ tableView: UITableView,
                         trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
        -> UISwipeActionsConfiguration? {
        let deleteAction = UIContextualAction(style: .destructive, title: "ลบ") {
            [weak self] _, _, completion in
            self?.contacts.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
            completion(true)
        }
        return UISwipeActionsConfiguration(actions: [deleteAction])
    }
}

ประสิทธิภาพของ Delegate: วิธีการกำหนดขนาด (heightForRowAt) จะถูกเรียกสำหรับทุกแถวที่มองเห็นได้ทุกครั้งที่เลื่อน สำหรับความสูงที่เท่ากัน ให้ตั้งค่า rowHeight โดยตรง — เร็วกว่า heightForRowAt ถึง 10 เท่า สำหรับเซลล์ปรับขนาดอัตโนมัติ ให้ตั้งค่า estimatedRowHeight และ automaticDimension — ตารางจะเรียก heightForRowAt เฉพาะแถวที่มองเห็นและใช้ estimatedRowHeight สำหรับแถวที่เหลือ

การใช้เซลล์ซ้ำ: คิวรีไซเคิลและการลงทะเบียน

คิวรีไซเคิล — กลุ่มของเซลล์ UITableView ที่นำมาใช้ซ้ำได้ เมื่อเซลล์เลื่อนออกจากหน้าจอ เซลล์จะไม่ถูกลบแต่จะถูกวางในคิว เซลล์ใหม่ไม่ได้ถูกสร้างขึ้นจากศูนย์ — ระบบเรียก dequeueReusableCell(withIdentifier:for:) ซึ่งจะส่งคืนเซลล์จากคิวหรือสร้างใหม่หากคิวว่าง นี่คือกลไกประสิทธิภาพหลักของ UITableView

swift
// เซลล์ที่กำหนดเองพร้อมแคช
class ContactCell: UITableViewCell {

    private let avatarView = UIImageView()
    private let nameLabel = UILabel()
    private let subtitleLabel = UILabel()

    override init(style: UITableViewCell.Style, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupViews()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupViews() {
        contentView.addSubview(avatarView)
        contentView.addSubview(nameLabel)
        contentView.addSubview(subtitleLabel)

        // การกำหนดค่าข้อจำกัด Auto Layout
        avatarView.translatesAutoresizingMaskIntoConstraints = false
        // ... constraints ...
    }

    func configure(with contact: Contact) {
        nameLabel.text = contact.name
        subtitleLabel.text = contact.phone
        // การโหลดอวตารแบบอะซิงโครนัส
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        avatarView.image = nil
        nameLabel.text = nil
        subtitleLabel.text = nil
    }
}

// การลงทะเบียนและการใช้งาน
tableView.register(ContactCell.self, forCellReuseIdentifier: "contactCell")

// ใน cellForRowAt:
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactCell
cell.configure(with: contacts[indexPath.row])
return cell

prepareForReuse() — วิธีการที่เรียกก่อนวางเซลล์ในคิวรีไซเคิล ให้รีเซ็ตสถานะทั้งหมดของเซลล์: ข้อความ รูปภาพ แอนิเมชัน การดำเนินการโหลด หากไม่มี prepareForReuse เซลล์ที่นำมาใช้ซ้ำอาจแสดงข้อมูลเก่า: รูปภาพก่อนหน้าจะปรากฏชั่วขณะจนกว่ารูปภาพใหม่จะโหลด Apple (WWDC 2023) แนะนำให้ยกเลิกการดำเนินการแบบอะซิงโครนัสผ่าน URLSessionTask.cancel() ใน prepareForReuse

ส่วน ส่วนหัว และส่วนท้าย: การจัดกลุ่มข้อมูล

ส่วน ใน UITableView จัดกลุ่มแถวที่เกี่ยวข้องกันตามตรรกะ แต่ละส่วนสามารถมีส่วนหัว (header) และส่วนท้าย (footer) จำนวนส่วนจะถูกกำหนดโดยวิธีการ numberOfSections(in:) (ค่าเริ่มต้นคือ 1) สำหรับสไตล์ grouped และ insetGrouped ส่วนต่างๆ จะถูกแยกด้วยสายตาด้วยระยะขอบและมุมมน

วิธีการ DataSourceคำอธิบายค่าที่ส่งคืน
numberOfSectionsจำนวนส่วน (ค่าเริ่มต้น 1)Int
numberOfRowsInSectionจำนวนแถวในส่วนInt
titleForHeaderInSectionข้อความชื่อส่วนหัวของส่วนString?
titleForFooterInSectionข้อความชื่อส่วนท้ายของส่วนString?
viewForHeaderInSectionมุมมองที่กำหนดเองสำหรับส่วนหัวUIView?
heightForHeaderInSectionความสูงของส่วนหัวCGFloat

การทำดัชนี: สำหรับการนำทางที่รวดเร็วระหว่างส่วนต่างๆ ให้เพิ่มชื่อดัชนี — อาร์เรย์ของสตริงที่แสดงทางด้านขวา ใช้ sectionIndexTitles(for:) — ส่งคืนอาร์เรย์ของตัวอักษรแรกของแต่ละส่วน สำหรับ 26+ ส่วน สิ่งนี้ช่วยปรับปรุง UX อย่างมาก ใน iOS 15+ ตารางรองรับ sectionHeaderTopPadding — ระยะห่างระหว่างแถบสถานะและส่วนหัวแรก ซึ่งสามารถรีเซ็ตเป็น 0 สำหรับเลย์เอาต์ที่แน่นหนา

ประสิทธิภาพ UITableView: แนวทางปฏิบัติที่ดีที่สุด

ประสิทธิภาพ ของ UITableView มีความสำคัญสำหรับแอปที่มีรายการยาว ปัญหาทั่วไป: การเลื่อนช้า (jank) เนื่องจากการดำเนินการหนักใน cellForRowAt การเรียก heightForRowAt บ่อยครั้ง การขาด prefetching Apple (WWDC 2025) เน้นย้ำแนวทางปฏิบัติสำคัญห้าประการสำหรับตารางที่มีมากกว่า 1,000 รายการ

  • ความสูงคงที่ — ใช้ rowHeight คงที่แทน heightForRowAt สำหรับทุกเซลล์ ความแตกต่าง: 60 FPS เทียบกับ 30 FPS สำหรับตาราง 500 แถว
  • Prefetching — ใช้ UITableViewDataSourcePrefetching สำหรับการโหลดข้อมูลแบบอะซิงโครนัส (รูปภาพ, API) ก่อนที่เซลล์จะปรากฏบนหน้าจอ
  • มุมมองย่อยน้อยลง — มุมมองย่อยแต่ละรายการใน contentView จะเพิ่มเวลาการเรนเดอร์ ใช้ draw() สำหรับกราฟิกง่ายๆ (ตัวคั่น, ไอคอน) แทน UIImageView
  • การดำเนินการหนักในพื้นหลัง — การจัดรูปแบบวันที่ การปรับภาษา การคำนวณควรทำในโมเดล ไม่ใช่ใน cellForRowAt ใช้ NSCache สำหรับผลลัพธ์
  • DiffableDataSource — แทนที่ reloadData() ด้วย apply(snapshot) — แอนิเมชันเกิดขึ้นได้ฟรี ตารางไม่ต้องโหลดซ้ำทั้งหมด
swift
// การเพิ่มประสิทธิภาพด้วย prefetching
extension ContactListController: UITableViewDataSourcePrefetching {

    func tableView(_ tableView: UITableView,
                         prefetchRowsAt indexPaths: [IndexPath]) {
        for indexPath in indexPaths {
            let contact = contacts[indexPath.row]
            ImageCache.shared.prefetch(url: contact.avatarUrl)
        }
    }

    func tableView(_ tableView: UITableView,
                         cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
        for indexPath in indexPaths {
            let contact = contacts[indexPath.row]
            ImageCache.shared.cancelPrefetch(url: contact.avatarUrl)
        }
    }
}

// การใช้ DiffableDataSource เพื่อการอัปเดตที่ราบรื่น
class ModernTableController: UIViewController {

    private var dataSource: UITableViewDiffableDataSource<Section, Contact>!

    func applyContacts(_ contacts: [Contact]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Contact>()
        snapshot.appendSections([.main])
        snapshot.appendItems(contacts)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

ผลลัพธ์ในทางปฏิบัติ: การใช้การเพิ่มประสิทธิภาพเหล่านี้ช่วยเพิ่ม FPS การเลื่อนจาก 30 เป็น 60 บนอุปกรณ์ iPhone 12 และรุ่นใหม่กว่า (ตามข้อมูลการทดสอบของ Apple, 2025) ในโครงการ IT Sectr เราใช้ DiffableDataSource สำหรับตารางใหม่ทั้งหมด และเพิ่ม prefetching สำหรับตารางที่มีรูปภาพ (อวตาร, ตัวอย่าง, ไอคอนแอป)

คำถามที่พบบ่อย

UITableView ไม่เลื่อน — ต้องทำอย่างไร

ตรวจสอบ: (1) contentSize > frame.size — ตารางต้องมีเนื้อหามากกว่าความสูงของมัน (2) ตารางไม่ได้อยู่ภายใน UIScrollView อื่นที่ดักจับท่าทาง (3) isScrollEnabled = true (4) numberOfRowsInSection ส่งคืนจำนวนที่ถูกต้อง ปัญหาทั่วไปคือตารางที่มี heightForRowAt = UITableView.automaticDimension โดยไม่มี estimatedRowHeight ซึ่งทำให้คำนวณความสูงไม่สิ้นสุด

จะอัปเดต UITableView โดยไม่กระพริบได้อย่างไร

ใช้ performBatchUpdates หรือ DiffableDataSource แทน reloadData() reloadData() ทำให้โหลดซ้ำทั้งหมดโดยไม่มีแอนิเมชัน สร้างการกระพริบที่มองเห็นได้ สำหรับการอัปเดตเฉพาะจุดให้ใช้: reloadRows(at:with:), insertRows, deleteRows การเปลี่ยนแปลงทั้งหมดภายใน performBatchUpdates จะถูกทำให้เคลื่อนไหวพร้อมกัน DiffableDataSource ทำสิ่งนี้โดยอัตโนมัติโดยไม่ต้องเรียกด้วยตนเอง

ความแตกต่างระหว่าง UITableView และ UICollectionView คืออะไร

UITableView เป็นรายการแนวตั้งคอลัมน์เดียวที่มีสไตล์เซลล์ที่กำหนดไว้ล่วงหน้าและการแก้ไขในตัว UICollectionView เป็นเลย์เอาต์ที่ยืดหยุ่น (กริด รายการ คาราเซล น้ำตก) พร้อมการวางตำแหน่งองค์ประกอบตามต้องการ เลือก UITableView สำหรับรายการอย่างง่าย (การตั้งค่า, รายชื่อ, ข้อความ) เลือก UICollectionView สำหรับแกลเลอรี แคตตาล็อก บอร์ด และเลย์เอาต์ที่ไม่เป็นเชิงเส้น

จะลบแถวว่างใต้เนื้อหาใน UITableView ได้อย่างไร

ตั้งค่า tableView.tableFooterView = UIView(frame: .zero) โดยค่าเริ่มต้น UITableView จะแสดงแถวว่าง (ตัวคั่น) ใต้รายการสุดท้ายจนถึงขอบด้านล่าง การตั้งค่า UIView ว่างเป็น tableFooterView จะลบออก สำหรับสไตล์ grouped ไม่จำเป็น — ตารางจะแสดงเฉพาะแถวจริงที่มีมุมมนในส่วนสุดท้าย

จะสร้าง UITableView ที่มีความสูงคงที่ได้อย่างไร

ตั้งค่า tableView.rowHeight = 80 และ tableView.estimatedRowHeight = 0 หรือละเว้น estimatedRowHeight ด้วยความสูงคงที่ UITableView จะไม่เรียก heightForRowAt ซึ่งช่วยเร่งการเรนเดอร์ 5–10 เท่าสำหรับรายการที่มีมากกว่า 100 รายการ สำหรับเซลล์ที่มีความสูงที่กำหนดเอง (เนื้อหาต่างกัน) ให้ใช้การปรับขนาดอัตโนมัติ: estimatedRowHeight + UITableView.automaticDimension

สรุป

  • UITableView — คอมโพเนนต์ UIKit หลักสำหรับรายการแนวตั้งพร้อมสถาปัตยกรรม DataSource และ Delegate
  • UITableViewCell — 4 สไตล์ในตัว (.default, .subtitle, .value1, .value2) และแบบกำหนดเองผ่าน contentConfiguration
  • คิวรีไซเคิล — กลุ่มของเซลล์ที่นำมาใช้ซ้ำได้ register + dequeueReusableCell + prepareForReuse เป็นสิ่งที่จำเป็น
  • DiffableDataSource (iOS 13+) — DataSource ที่ปลอดภัยพร้อมการคำนวณการเปลี่ยนแปลงอัตโนมัติและแอนิเมชัน
  • ประสิทธิภาพ: rowHeight คงที่, prefetching, มุมมองย่อยน้อยลง, การดำเนินการหนักในพื้นหลัง
  • 85% ของแอป iOS ใน 100 อันดับแรกของ App Store ใช้ UITableView ในการแสดงเนื้อหา
  • สำหรับกริด คาราเซล และเลย์เอาต์ที่ซับซ้อน ให้ใช้ UICollectionView แทน UITableView

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม