Как организовать секции и логику отображения UI и обработку отсутствия данных в контроллере
Что проверяют
Как вы моделируете состояние экрана и секции, чтобы контроллер не превратился в набор проверок data.isEmpty. Следом спрашивают, как обновлять список без reloadData() и без крашей при вставке и удалении строк.
Как отвечать
- состояние экрана: одно перечисление вроде
enum ViewState { case loading, content([Section]), empty, error(Error) }. Контроллер отрисовывает состояние, а не угадывает его по массивам; - секции как типы:
enum Section: Hashableс кейсамиheader,favorites,recent, элементы тожеHashable. Пустая секция просто не попадает в данные, а не висит с заголовком и нулём строк; - diffable data source:
UICollectionViewDiffableDataSourceилиUITableViewDiffableDataSource(iOS 13+). СобираетеNSDiffableDataSourceSnapshotчерезappendSectionsиappendItemsи вызываетеapply(_:animatingDifferences:). Рассинхрон числа строк с данными исчезает как класс; - раскладка:
UICollectionViewCompositionalLayoutзадаёт каждой секции свою раскладку, для списков естьUICollectionLayoutListConfiguration; - пустое состояние: не фейковая ячейка «Нет данных», а отдельный слой. С iOS 17 у
UIViewControllerестьcontentUnavailableConfigurationи готовыеUIContentUnavailableConfiguration.empty(),.loading(),.search(), на старых версиях подходитbackgroundViewтаблицы; - логика вне контроллера: ViewModel или Presenter собирает секции и состояние, контроллер только применяет снимок. Её легко тестировать без UI.
Различайте три случая: данные ещё грузятся, данных действительно нет, загрузка упала. Для каждого своё: индикатор, пояснение, кнопка повтора.
Где спотыкаются
Показывают «Нет данных» сразу при открытии. Пустой массив на старте значит «ещё не загрузили», и без явного состояния loading пользователь видит ложное сообщение.
Обращаются к секциям по номерам: if indexPath.section == 1. Стоит скрыть одну секцию, и все индексы съезжают.
Меняют массив и вызывают insertRows и deleteRows вразнобой, получая NSInternalInconsistencyException. Похожая ошибка: dequeueReusableCell(withIdentifier:for:) без предварительного register падает при первом же показе ячейки.