UITableView: βασικά, λίστες και κελιά σε εφαρμογές iOS

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-02-23 Χρόνος ανάγνωσης: 9 λεπ

UITableView — το κύριο στοιχείο του UIKit για την εμφάνιση κάθετων λιστών στο iOS. Κάθε γραμμή του πίνακα είναι ένα κελί UITableViewCell με προκαθορισμένα στυλ. Εξηγούμε τα βασικά του UITableView: DataSource, εκπρόσωπος, επαναχρησιμοποίηση κελιών και απόδοση πινάκων σε εφαρμογές iOS. Σύμφωνα με την Apple (Human Interface Guidelines, 2026), το UITableView παραμένει το πιο χρησιμοποιημένο στοιχείο UI στο iOS — 85% των εφαρμογών top-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 — όχι.

Self-Sizing Cells

Από το iOS 8, το UITableView υποστηρίζει κελιά με αυτόματο μέγεθος (self-sizing cells). Για ενεργοποίηση ορίστε tableView.estimatedRowHeight (π.χ. 80) και tableView.rowHeight = UITableView.automaticDimension. Ο πίνακας υπολογίζει αυτόματα το ύψος γραμμής βάσει των περιορισμών Auto Layout μέσα στο κελί. Το self-sizing μειώνει την απόδοση για σύνθετα κελιά — χρησιμοποιήστε σταθερό ύψος (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. Πλεονεκτήματα: υποστήριξη δυναμικού τύπου, σκοτεινού θέματος και VoiceOver από το κουτί. Η Apple (WWDC 2024) συνιστά το contentConfiguration για όλα τα νέα UITableView.

DataSource και Delegate: υποχρεωτικές μέθοδοι

UITableViewDataSource — πρωτόκολλο που παρέχει δεδομένα για τον πίνακα. Υποχρεωτικές μέθοδοι: tableView(_:numberOfRowsInSection:) και tableView(_:cellForRowAt:). Χωρίς αυτές, ο πίνακας δεν μπορεί να εμφανίσει καμία γραμμή. UITableViewDelegate — πρωτόκολλο για τη διαχείριση εμφάνισης και συμπεριφοράς: ύψος γραμμών, επιλογή, επεξεργασία, contextual actions.

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)
    }

    // Σύρσιμο για διαγραφή
    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 — αυτό είναι 10 φορές ταχύτερο από το heightForRowAt. Για κελιά self-sizing ορίστε estimatedRowHeight και automaticDimension — ο πίνακας θα καλέσει το heightForRowAt μόνο για ορατές γραμμές και για τις υπόλοιπες θα χρησιμοποιήσει το estimatedRowHeight.

Επαναχρησιμοποίηση κελιών: ουρά reuse και register

Ουρά reuse — δεξαμενή επαναχρησιμοποιήσιμων κελιών 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
        // ... περιορισμοί ...
    }

    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() — μέθοδος που καλείται πριν από την τοποθέτηση του κελιού στην ουρά reuse. Επαναφέρετε ολόκληρη την κατάσταση του κελιού: κείμενο, εικόνες, κινούμενα σχέδια, φορτώσεις. Χωρίς prepareForReuse, ένα επαναχρησιμοποιημένο κελί μπορεί να εμφανίζει παλιά δεδομένα: η προηγούμενη εικόνα θα είναι ορατή για ένα κλάσμα του δευτερολέπτου μέχρι να φορτωθεί η νέα. Η Apple (WWDC 2023) συνιστά τον καθαρισμό ασύγχρονων λειτουργιών μέσω URLSessionTask.cancel() στο prepareForReuse.

Ενότητες, header και footer: ομαδοποίηση δεδομένων

Ενότητες στο UITableView ομαδοποιούν λογικά συνδεδεμένες γραμμές. Κάθε ενότητα μπορεί να έχει header (κεφαλίδα) και footer (υποσέλιδο). Ο αριθμός ενοτήτων καθορίζεται από τη μέθοδο numberOfSections(in:) (προεπιλογή 1). Για στυλ grouped και insetGrouped, οι ενότητες διαχωρίζονται οπτικά με περιθώρια και στρογγυλεμένες γωνίες.

Μέθοδος DataSourceΠεριγραφήΤιμή επιστροφής
numberOfSectionsΑριθμός ενοτήτων (προεπιλογή 1)Int
numberOfRowsInSectionΑριθμός γραμμών στην ενότηταInt
titleForHeaderInSectionΚείμενο κεφαλίδας ενότηταςString?
titleForFooterInSectionΚείμενο υποσέλιδου ενότηταςString?
viewForHeaderInSectionΠροσαρμοσμένη προβολή για headerUIView?
heightForHeaderInSectionΎψος headerCGFloat

Ευρετηρίαση: για γρήγορη πλοήγηση μεταξύ ενοτήτων προσθέστε ένα ευρετήριο — μια συμβολοσειρά που εμφανίζεται στα δεξιά. Υλοποιήστε sectionIndexTitles(for:) — επιστρέφει μια συστοιχία από τα πρώτα γράμματα κάθε ενότητας. Για 26+ ενότητες αυτό βελτιώνει κρίσιμα το UX. Στο iOS 15+, οι πίνακες υποστηρίζουν sectionHeaderTopPadding — το διάστημα μεταξύ της γραμμής κατάστασης και της πρώτης κεφαλίδας που μπορεί να μηδενιστεί για συμπαγή διάταξη.

Απόδοση UITableView: βέλτιστες πρακτικές

Απόδοση Το UITableView είναι κρίσιμο για εφαρμογές με μεγάλες λίστες. Κύρια προβλήματα: αργή κύλιση (jank) λόγω βαριών λειτουργιών στο cellForRowAt, συχνές κλήσεις heightForRowAt, έλλειψη prefetching. Η Apple (WWDC 2025) επισημαίνει πέντε βασικές πρακτικές για πίνακες με 1000+ στοιχεία.

  • Flat-height — χρησιμοποιήστε σταθερό rowHeight αντί για heightForRowAt για όλα τα κελιά. Διαφορά: 60 FPS vs 30 FPS για πίνακα 500 γραμμών.
  • Prefetching — υλοποιήστε το UITableViewDataSourcePrefetching για ασύγχρονη φόρτωση δεδομένων (εικόνες, API) πριν εμφανιστεί το κελί στην οθόνη.
  • Λιγότερα subview — κάθε subview στο 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 για πίνακες με εικόνες (avatars, προεπισκοπήσεις, εικονίδια εφαρμογών).

Συχνές ερωτήσεις

Το 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+ στοιχεία. Για κελιά με προσαρμοσμένο ύψος (διαφορετικό περιεχόμενο) χρησιμοποιήστε self-sizing: estimatedRowHeight + UITableView.automaticDimension.

Σύνοψη

  • UITableView — το κύριο στοιχείο UIKit για κάθετες λίστες με αρχιτεκτονική DataSource και Delegate.
  • UITableViewCell — 4 ενσωματωμένα στυλ (.default, .subtitle, .value1, .value2) και προσαρμοσμένα μέσω contentConfiguration.
  • Ουρά reuse — δεξαμενή επαναχρησιμοποιήσιμων κελιών; register + dequeueReusableCell + prepareForReuse υποχρεωτικά.
  • DiffableDataSource (iOS 13+) — type-safe DataSource με αυτόματο υπολογισμό αλλαγών και κίνηση.
  • Απόδοση: σταθερό rowHeight, prefetching, λιγότερα subview, βαριές λειτουργίες στο παρασκήνιο.
  • Το 85% των εφαρμογών iOS top-100 App Store χρησιμοποιούν UITableView για εμφάνιση περιεχομένου.
  • Για πλέγματα, καρουζέλ και σύνθετες διατάξεις χρησιμοποιήστε UICollectionView αντί για UITableView.

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης