← Назад к вопросам

Как реализовываешь Delegate TableView?

1.0 Junior🔥 191 комментариев
#UIKit и верстка

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как реализовать Delegate для UITableView в iOS

Реализация делегата для UITableView является фундаментальной задачей при разработке iOS-приложений. Делегат позволяет таблице взаимодействовать с вашим кодом, определяя её поведение и содержимое. Основные компоненты — это UITableViewDataSource и UITableViewDelegate, которые часто реализуются в одном классе (например, контроллере или отдельном объекте).

Основные шаги реализации

1. Подключение делегата и источника данных

class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
    }
}

2. Реализация UITableViewDataSource

Этот протокол отвечает за предоставление данных и базовой структуры таблицы.

extension ViewController: UITableViewDataSource {
    // Определяет количество строк в секции
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataArray.count
    }
    
    // Создает и конфигурирует каждую ячейку
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = dataArray[indexPath.row]
        return cell
    }
    
    // Опционально: количество секций
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    // Опционально: заголовок секции
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "Моя секция"
    }
}

3. Реализация UITableViewDelegate

Этот протокол управляет внешним видом и поведением таблицы, обработкой взаимодействий.

extension ViewController: UITableViewDelegate {
    // Обработка выбора строки
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Выбрана строка: \(indexPath.row)")
        tableView.deselectRow(at: indexPath, animated: true)
    }
    
    // Определение высоты строки
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60.0
    }
    
    // Опционально: заголовок секции с кастомным видом
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 40))
        headerView.backgroundColor = .lightGray
        let label = UILabel(frame: headerView.bounds)
        label.text = "Кастомный заголовок"
        headerView.addSubview(label)
        return headerView
    }
    
    // Опционально: действия для свайпа (iOS 11+)
    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let deleteAction = UIContextualAction(style: .destructive, title: "Удалить") { (action, view, completion) in
            self.dataArray.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
            completion(true)
        }
        return UISwipeActionsConfiguration(actions: [deleteAction])
    }
}

Ключевые принципы и лучшие практики

  • Разделение ответственности: В сложных приложениях можно создать отдельный класс для DataSource (например, TableDataManager), чтобы не загрязнять контроллер.
  • Эффективное использование ячеек: Метод dequeueReusableCell предотвращает создание новых объектов для каждой строки, что критично для производительности.
  • Динамическая высота ячейки: Для ячеек с переменным содержимым используйте UITableView.automaticDimension вместе с корректно настроенными Auto Layout ограничениями внутри ячейки.
  • Обработка множественных секций: Для таблиц с несколькими секциями требуется тщательно управлять индексами и организовывать данные (часто используют массивы массивов или специальные структуры данных).
  • Кастомные ячейки: Практически всегда используются собственные классы ячеек вместо стандартных UITableViewCell. Регистрация происходит через:
tableView.register(CustomCell.nib, forCellReuseIdentifier: CustomCell.identifier)
// или для ячейки, созданной кодом:
tableView.register(CustomCell.self, forCellReuseIdentifier: CustomCell.identifier)

Пример с кастомной ячейкой и разделением логики

// Кастомная ячейка
class CustomCell: UITableViewCell {
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var subtitleLabel: UILabel!
    
    func configure(with item: DataModel) {
        titleLabel.text = item.title
        subtitleLabel.text = item.subtitle
    }
}

// Отдельный источник данных
class TableDataSource: NSObject, UITableViewDataSource {
    var items: [DataModel]
    
    init(items: [DataModel]) {
        self.items = items
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as? CustomCell else {
            return UITableViewCell()
        }
        cell.configure(with: items[indexPath.row])
        return cell
    }
}

// Использование в контроллере
class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    private var dataSource: TableDataSource!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let items = [DataModel(title: "Заголовок 1", subtitle: "Подзаголовок 1")]
        dataSource = TableDataSource(items: items)
        tableView.dataSource = dataSource
        tableView.delegate = self
        tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "CustomCell")
    }
}

Современные подходы (iOS 13+)

Для более декларативного и безопасного управления данными можно использовать Diffable Data Source:

var diffableDataSource: UITableViewDiffableDataSource<Section, Item>!

func setupDiffableDataSource() {
    diffableDataSource = UITableViewDiffableDataSource<Section, Item>(tableView: tableView) { tableView, indexPath, item in
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = item.title
        return cell
    }
    
    var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
    snapshot.appendSections([.main])
    snapshot.appendItems(itemsArray)
    diffableDataSource.apply(snapshot, animatingDifferences: true)
}

Дифференцированный источник данных автоматически управляет изменениями содержимого с анимацией, избегая ошибок с индексами.

Решение распространенных проблем

  • Не забывайте вызывать tableView.reloadData() после изменения массива данных для обновления интерфейса.
  • Для динамического контента используйте beginUpdates/endUpdates для batch изменений или performBatchUpdates в современных версиях.
  • При работе с Core Data или большими массивами реализуйте пагинацию или постепенную загрузку для избежания задержек.
  • Всегда обрабатывайте didSelectRowAt для обеспечения обратной связи пользователю (обычно с deselectRow).

Таким образом, правильная реализация делегата UITableView требует понимания разделения между предоставлением данных (DataSource) и реагированием на действия (Delegate), использования кастомных ячеек для сложных интерфейсов, и соблюдения принципов эффективности для обеспечения плавной работы таблицы даже с большими объемами данных. В современных приложениях также стоит рассматривать использование UITableViewDiffableDataSource для более надежного и декларативного управления состоянием таблицы.

Как реализовываешь Delegate TableView? | PrepBro