Как правильно показывать и обрабатывать ошибки в UI iOS
Что проверяют
Умеете ли вы отделить ошибку как событие в коде от того, что увидит пользователь. Следом обычно спрашивают, какие ошибки вообще не надо показывать и как обработка устроена у вас в архитектуре.
Как отвечать
Сначала классификация, потом UI:
- восстановимые пользователем: нет сети, неверный ввод, истекла сессия. Показываете понятный текст и действие: «Повторить», переход к полю, повторный вход;
- временные: таймаут, 5xx. Можно тихо повторить с нарастающей паузой и показать ошибку, только если повтор не помог;
- неожиданные: ошибка декодирования, нарушенный инвариант. Пользователю общий текст, в лог полная причина.
Форма показа зависит от контекста. Ошибка валидации живёт рядом с полем. Не загрузился весь экран: состояние ошибки вместо контента с кнопкой повтора. Не прошло фоновое действие: неблокирующий баннер. UIAlertController или SwiftUI-модификатор .alert(isPresented:error:actions:) оставьте для случаев, когда пользователь должен принять решение.
В коде слой данных бросает доменные ошибки (со Swift 6 можно typed throws, throws(NetworkError)), а URLError с кодами .notConnectedToInternet или .timedOut маппится в них. Тексты даёт LocalizedError через errorDescription и recoverySuggestion. Экран держит состояние enum State { case loading, loaded(Model), failed(AppError) }, и ошибка становится одним из вариантов рендера, а не побочным эффектом. Обновление UI идёт на @MainActor. Для VoiceOver текст ошибки стоит объявить через UIAccessibility.post(notification: .announcement, argument:). Параллельно ошибка уходит в os.Logger и систему крэш-репортинга с контекстом.
Где спотыкаются
Показывают алерт на отмену. Пользователь ушёл с экрана, задача отменилась, и всплывает «Ошибка» из-за CancellationError или URLError.cancelled. Отмену надо отфильтровать до показа.
Выводят error.localizedDescription как есть. У собственной ошибки без LocalizedError это системное «The operation couldn’t be completed» с именем типа и номером, пользователю оно ничего не говорит.
Глотают ошибку через try? или пустой catch. Экран просто остаётся пустым, и ни пользователь, ни лог не знают почему.