UITableView: الأساسيات، القوائم والخلايا في تطبيقات iOS

المؤلف: IT Sectr نُشر: 2026-02-23 وقت القراءة: 9 دق

UITableView — المكون الرئيسي في UIKit لعرض القوائم الرأسية في iOS. كل صف في الجدول هو خلية UITableViewCell بأنماط محددة مسبقاً. نشرح أساسيات UITableView: مصدر البيانات، المفوض، إعادة استخدام الخلايا وأداء الجداول في تطبيقات iOS. وفقاً لشركة Apple (إرشادات الواجهة البشرية، 2026)، لا يزال UITableView أكثر مكونات UI استخداماً في iOS — 85% من التطبيقات في أفضل 100 متجر App Store تحتوي على جدول واحد على الأقل. للتخطيطات المعقدة، اقرأ المقال عن UICollectionView.

النقاط الرئيسية

  • UITableView — مكون UIKit للقوائم الرأسية، متاح منذ iOS 2.0 (iPhone OS 2).
  • UITableViewCell — خلية جدول بأربعة أنماط مدمجة وخيارات تخصيص.
  • UITableViewDataSource — بروتوكول يحدد عدد الصفوف والخلايا لكل قسم.
  • UITableViewDelegate — بروتوكول لمعالجة التحديد والتحرير وتكوين ارتفاع الصفوف.
  • UITableViewDiffableDataSource — مصدر بيانات حديث (iOS 13+) مع حساب تلقائي للتغييرات.

ما هو UITableView في iOS؟

UITableView هو فئة من إطار عمل UIKit مصممة لعرض قائمة قابلة للتمرير من الصفوف في عمود واحد. كل صف يمثله كائن UITableViewCell. ظهر UITableView لأول مرة في iPhone OS 2 (2008) وظل منذ ذلك الحين المكون الأساسي للقوائم في iOS. بنية UITableView مبنية على نمط MVC: البيانات تُدار بواسطة مصدر البيانات، المظهر والسلوك بواسطة المفوض، والعرض بواسطة الجدول نفسه.

نمطان للجدول: .plain (قائمة مستمرة مع أقسام اختيارية) و .grouped (أقسام مجمعة بزوايا مدورة وهوامش). iOS 13 أضاف .insetGrouped — نمط مجمع بهوامش من الحواف، يُستخدم في تطبيقي الإعدادات والصحة. يؤثر اختيار النمط على المظهر الافتراضي: الجداول من النوع 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 الجديدة.

مصدر البيانات والمفوض: الطرق الإلزامية

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

أداء المفوض: طرق الحجم (heightForRowAt) تُستدعى لكل صف مرئي عند كل تمرير. لارتفاع موحد، قم بتعيين rowHeight مباشرة — هذا أسرع 10 مرات من heightForRowAt. للخلايا ذاتية الحجم، قم بتعيين 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، يتم فصل الأقسام بصرياً بهوامش وزوايا مدورة.

طريقة مصدر البياناتالوصفقيمة الإرجاع
numberOfSectionsعدد الأقسام (الافتراضي 1)Int
numberOfRowsInSectionعدد الصفوف في القسمInt
titleForHeaderInSectionنص عنوان رأس القسمString?
titleForFooterInSectionنص عنوان تذييل القسمString?
viewForHeaderInSectionعرض مخصص للرأسUIView?
heightForHeaderInSectionارتفاع الرأسCGFloat

الفهرسة: للتنقل السريع بين الأقسام، أضف عنوان فهرس — مصفوفة من السلاسل المعروضة على الجانب الأيمن. نفذ sectionIndexTitles(for:) — يُرجع مصفوفة من الأحرف الأولى لكل قسم. لـ 26+ قسماً، هذا يحسن تجربة المستخدم بشكل كبير. في iOS 15+، تدعم الجداول sectionHeaderTopPadding — الحشو بين شريط الحالة وأول رأس، والذي يمكن إعادة تعيينه إلى 0 لتخطيط كثيف.

أداء UITableView: أفضل الممارسات

أداء UITableView أمر بالغ الأهمية للتطبيقات ذات القوائم الطويلة. المشكلات الشائعة: التمرير البطيء (jank) بسبب العمليات الثقيلة في cellForRowAt، الاستدعاءات المتكررة لـ heightForRowAt، عدم وجود prefetching. تبرز Apple (WWDC 2025) خمس ممارسات رئيسية للجداول التي تحتوي على 1000+ عنصر.

  • ارتفاع ثابت — استخدم rowHeight ثابت بدلاً من heightForRowAt لجميع الخلايا. الفرق: 60 إطاراً في الثانية مقابل 30 إطاراً لجدول من 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)
    }
}

النتيجة العملية: تطبيق هذه التحسينات يزيد معدل الإطارات في التمرير من 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 للقوائم الرأسية ببنية مصدر بيانات ومفوض.
  • UITableViewCell — 4 أنماط مدمجة (.default، .subtitle، .value1، .value2) ومخصصة عبر contentConfiguration.
  • قائمة إعادة الاستخدام — مجموعة من الخلايا القابلة لإعادة الاستخدام؛ register + dequeueReusableCell + prepareForReuse إلزامية.
  • DiffableDataSource (iOS 13+) — مصدر بيانات type-safe مع حساب تلقائي للتغييرات ورسوم متحركة.
  • الأداء: rowHeight ثابت، prefetching، عدد أقل من العروض الفرعية، عمليات ثقيلة في الخلفية.
  • 85% من تطبيقات iOS في أفضل 100 متجر App Store تستخدم UITableView لعرض المحتوى.
  • للشبكات، الدوارات والتخطيطات المعقدة، استخدم UICollectionView بدلاً من UITableView.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا