В этом году на WWDC Apple удивила разработчиков неожиданным улучшением UIKit. Представление UIContentUnavailableConfiguration было направлено на упрощение процесса создания пустых состояний для контроллеров представления.
По словам Apple, UIContentUnavailableConfiguration представляет собой composable описание пустого состояния и может быть снабжено содержимым-заполнителем, например, изображением или текстом.
Вот пример пустого состояния, продемонстрированный на WWDC:
Теперь давайте разберемся в деталях.
Примечание: Эта статья в основном посвящена UIKit. Если вы хотите узнать, как сделать то же самое в SwiftUI, рекомендую ознакомиться с этой статьей Антуана ван дер Ли.
Содержание
Создание UIContentUnavailableConfiguration
Существует 4 способа создания UIContentUnavailableConfiguration:
Создание с нуля
Использование предопределенной конфигурации загрузки
Использование предопределенной конфигурации поиска
Использование конфигурации UIHostingConfiguration
1. Создание с нуля
Чтобы начать с нуля, мы должны сначала создать пустую UIContentUnavailableConfiguration.
var config = UIContentUnavailableConfiguration.empty()
После этого нам нужно будет настроить содержимое UIContentUnavailableConfiguration в соответствии с нашими потребностями:
Вот как это выглядит после вышеуказанной настройки:
3. Использование предопределенной конфигурации поиска
Еще одна очень полезная предопределенная конфигурация — это конфигурация поиска. Мы можем использовать ее, когда хотим показать пустое состояние для результата поиска:
var config = UIContentUnavailableConfiguration.search()
self.contentUnavailableConfiguration = config
Подобно конфигурации загрузки, содержимое-заполнитель конфигурации поиска также можно настраивать.
4. Использование UIHostingConfiguration
И наконец, мой личный фаворит — использование UIHostingConfiguration. Этот подход, по сути, позволяет нам создать любой пустой макет состояния, который мы хотим, используя SwiftUI.
Приведенный выше код даст нам следующий результат:
Профессиональный совет: Чтобы узнать больше о UIHostingConfiguration, я рекомендую ознакомиться с этими статьями.
Обновление contentUnavailableConfiguration контроллера представления
Когда дело доходит до обновления contentUnavailableConfiguration, Apple рекомендует разработчикам переопределить новый метод обновления под названием updateContentUnavailableConfiguration(using:).
На данный момент нет официальной документации, определяющей, когда именно будет вызываться этот метод. Однако, по моим наблюдениям, метод вызывается каждый раз, когда загружается контроллер представления.
В ситуациях, когда нам нужно вручную вызвать метод обновления, мы можем вызвать следующую функцию:
setNeedsUpdateContentUnavailableConfiguration()
Пример использования в реальной жизни
На основе того, что мы только что обсудили, я создал пример приложения, чтобы показать, как использовать UIContentUnavailableConfiguration для отображения пустого состояния, когда приложение либо загружается, либо сталкивается с ошибкой.
Для дальнейшего повышения интерактивности примера приложения я также добавил кнопку перезагрузки (которая также является частью UIContentUnavailableConfiguration) в пустое состояние ошибки. Эта кнопка перезагрузки позволяет пользователям легко обновить содержимое приложения в случае ошибки.
Вот полный код примера, если вам интересно. Обязательно запустите его на Xcode 15 beta 1 или более поздней версии.
import UIKit
class ContentUnavailableViewController: UIViewController {
/// Variable to keep track of content fetching state
/// nil means content is not yet fetched
var fetchContentSuccessful: Bool? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Create fetch button
let fetchAction = UIAction(handler: { [weak self]_ in
self?.startFetchCotent()
})
let fetchButton = UIBarButtonItem(title: "Fetch", primaryAction: fetchAction)
navigationItem.rightBarButtonItem = fetchButton
}
override func updateContentUnavailableConfiguration(
using state: UIContentUnavailableConfigurationState
) {
// Remove existing configuration (if exist)
contentUnavailableConfiguration = nil
guard let fetchContentSuccessful else {
// User have not trigger fetch
return
}
if fetchContentSuccessful {
// Prompt alert view
showContent()
} else {
// Show empty state
showError()
}
}
/// Start the fetch content flow
private func startFetchCotent() {
Task { [weak self] in
guard let self = self else { return }
showLoading()
fetchContentSuccessful = await fetchContent()
// Update UI
setNeedsUpdateContentUnavailableConfiguration()
}
}
/// Action to run after successfully fetch content
private func showContent() {
let alert = UIAlertController(
title: "🎉🎉🎉",
message: "Fetch successful!",
preferredStyle: .alert
)
let positiveAction = UIAlertAction(
title: "OK",
style: .default
)
alert.addAction(positiveAction)
present(alert, animated: true)
}
/// Show the loading empty state
private func showLoading() {
var config = UIContentUnavailableConfiguration.loading()
config.text = "Fetching content. Please wait..."
config.textProperties.font = .boldSystemFont(ofSize: 18)
self.contentUnavailableConfiguration = config
}
/// Show the empty state when encounter error
private func showError() {
var errorConfig = UIContentUnavailableConfiguration.empty()
errorConfig.image = UIImage(systemName: "exclamationmark.circle.fill")
errorConfig.text = "Something went wrong."
errorConfig.secondaryText = "Please try again later."
// Create configuration for reload button
var retryButtonConfig = UIButton.Configuration.borderless()
retryButtonConfig.image = UIImage(systemName: "arrow.clockwise.circle.fill")
errorConfig.button = retryButtonConfig
// Define the reload button action
errorConfig.buttonProperties.primaryAction = UIAction.init(handler: { _ in
Task { [weak self] in
guard let self = self else { return }
startFetchCotent()
}
})
contentUnavailableConfiguration = errorConfig
}
/// A dummy function to simulate the fetch content action
private func fetchContent() async -> Bool {
// Sleep for 1 minutes to simulate a slow API call
try? await Task.sleep(nanoseconds: 1_000_000_000)
return Bool.random()
}
}
Уменьшая трение, связанное с обработкой пустых состояний, Apple эффективно устраняет одно из распространенных оправданий, по которым разработчики пренебрегали этим аспектом дизайна приложений. Компания поощряет разработчиков брать на себя ответственность за то, чтобы пользовательский интерфейс их приложений не оставался в унылом, необработанном состоянии.
10 октября в 19:00 в московском офисе Авито пройдёт Avito iOS meetup #10. Спикеры из Авито, МТС и Тинькофф Страхования расскажут про подход async/await, фреймворк SwiftUI и mergeable libraries. В
Информация о мероприятии
10 октября в 19:00 в московском офисе Авито пройдёт Avito iOS meetup #10. Спикеры из Авито, МТС и Тинькофф Страхования расскажут про подход async/await, фреймворк SwiftUI и mergeable libraries. В докладах будет теория и реальные кейсы из опыта спикеров, которые можно будет обсудить после выступлений.
Программа
18:30–19:00 | Регистрация. Сбор гостей
19:05–19:35 | Async / Await. Лучшие практики, советы и особенности — Максим Сурков, МТС
19:35–20:05 | SwiftUI — уже пора? — Тимур Долотказин, Авито
20:05–20:30 | Кофе-брейк
20:30–21:00 | Гармония статики и динамики: погружаемся в mergeable libraries — Александр Сычев, Тинькофф Страхование
Легендарная конференция МАМА (Mobile Attribution & Marketing Analytics) отправляется в Грузию!
17 октября на сцене МАМА в Тбилиси выступят спикеры из Wargaming, inDrive, Prequel, Bolt, TBC Bank и Hepsiburada. Хедлайнер –
Информация о мероприятии
Легендарная конференция МАМА (Mobile Attribution & Marketing Analytics) отправляется в Грузию!
17 октября на сцене МАМА в Тбилиси выступят спикеры из Wargaming, inDrive, Prequel, Bolt, TBC Bank и Hepsiburada. Хедлайнер – блогер Nuseir Yassin с 60 миллионами подписчиков по всему миру, который проведет сессию для мобильных брендов по работе с соцсетями и инфлюенсерами.
Язык: английский с синхронным переводом на грузинский и русский
Формат: только оффлайн
Вход свободный, но нужно получить подтверждение, поскольку конференция эксклюзивно для владельцев приложений.