Часть вторая: асинхронные данные и удаленные образы

Это вторая часть моего сборника статей о создании реальных приложений только с использованием SwiftUI. Часть 1 - это введение в поток данных и Redux, а также обзор приложения - это важное чтение, если вы хотите полностью понять эту статью.

В этом посте подробно рассказывается, как получать данные из API и отображать удаленные изображения в SwiftUI.

Поскольку MovieSwiftUI полагается на TMDb API для отображения информации о фильмах, получение удаленных данных является ключевым моментом. В то время как пользовательский список фильмов, список желаний и просмотренный, настройки и т. Д. Обрабатываются и сохраняются локально, другая информация извлекается удаленно.

Асинхронные данные

В этой части особо нечего сказать о SwiftUI, поскольку я использую шаблон Redux. Полученные данные в основном сводятся к состоянию моего приложения, а затем состояние публикуется, поэтому представления обновляются с новыми данными. Но как это на самом деле работает? Давайте взглянем.

Я сделал очень простой класс APIService, который использует URLSession для выполнения запросов. Все мои модели соответствуют Codable и являются практически однозначным представлением моделей из TMDb API. Таким образом, мне не нужны специальные декодеры или переходные модели для представлений. В приложении есть несколько мест, где я использую чистые модели представления, но по большей части ответ сериализуется в состоянии приложения логическим способом (например, база данных в памяти, если вы предпочитаете) редукторами.

Затем у меня есть AsyncAction, которые представляют собой действия Redux, которые вы можете отправлять как обычно, однако они не сокращаются редукторами (они могут, например, для обеспечения состояния загрузки), но они выполняются промежуточным программным обеспечением для запуска запроса, который они реализовать в своей функции execute (). После завершения запроса они связывают другое действие, чтобы обновить состояние на основе данных, содержащихся в ответе.

Действия:

Промежуточное ПО предоставляется моим пакетом Swift SwiftUIFlux. Запускать функцию выполнения из AsyncAction по мере ее отправки.

Наконец, редуктор добавляет фильм в свойство state movies.

Так что насчет просмотров? Давайте посмотрим на мой просмотр MovieDetail:

Я получаю свое хранилище с помощью EnvironmentObject - доступного, потому что он внедрен в корневое представление моего приложения. Затем я получаю данные, которые мне нужны, когда запускается .onAppear () представлений (у Криса Эйдхофа есть другой подход, когда он запускает загрузку ресурсов, поскольку SwiftUI подписывается на его ObservableObject). Поскольку я сопоставляю свойства своего состояния с вычисляемыми свойствами представлений, все в объекте фильма, обновленном с помощью AsyncAction, будет обновлено в моем представлении MovieDetail и его различных компонентах.

Я надеюсь, что эта часть дает четкий обзор того, как я выполняю вызовы API и удаленную загрузку данных в мое приложение SwiftUI. Теперь мы немного подробнее рассмотрим SwiftUI с компонентом Image, предоставляемым Apple, и узнаем, как заставить его работать с удаленными образами.

Удаленные изображения

Компонент Изображение от Apple действительно хорош - он поддерживает хороший набор форматов ввода, таких как Data, UIImage и SF Symbols. Однако он не поддерживает инициализацию с URL-адресом (тем не менее? Я отправил отзыв в Apple и надеюсь, что он будет добавлен до сентябрьского выпуска или в следующем выпуске.)

Чтобы отображать изображения, полученные из удаленного места, как и UIImageView, вам нужно создать свою собственную систему. Это упрощается благодаря ObservableObject и оболочке свойств @ObservedObject.

Подобно сетевым вызовам, я сделал ImageService. (Это не слишком актуально для этого поста, поэтому код ниже не включен). Это базовый менеджер, который асинхронно загружает данные с URL-адреса и публикует их для всех подписчиков, использующих платформу Combine.

Интересно то, как он интегрирован со SwiftUI. Для этой цели я создал класс ImageLoader, который соответствует протоколу ObservableObject, поэтому SwiftUI может получать уведомления о любых изменениях свойств @Published.

Вы создаете его экземпляр с путем, размером, и он вызывает ImageService для получения изображения из Интернета. Затем он установит его локально в свойстве ImageLoader image (UIImage), которое будет публиковать свои изменения для всех, кто на него подписан. А на что можно подписаться? Вид!

Выше у меня также есть ImageLoaderCache, который будет кэшировать мои экземпляры ImageLoader, поскольку они могут быть уничтожены, потому что на них ничего не ссылается как тело представлений перезагрузки SwiftUI. Это также позволяет избежать многократной загрузки одного и того же изображения и разрешить повторное использование.

Вот конкретная реализация View, которую вы можете напрямую использовать в любом приложении SwiftUI. Он использует ImageLoader, который использует ImageService для отображения изображения. Он даже предоставляет заполнитель и плавно заменяет его после загрузки изображения с базовой анимацией постепенного появления.

Он запускает вызов ImageLoader .loadImage () из функции .onAppear () из инфраструктуры SwiftUI.

Важно отметить, что только компонент List ведет себя как UITableView. Это означает, что представления в списке ставятся в очередь и удаляются / повторно используются при прокрутке. Таким образом, .onAppear () будет вызываться только тогда, когда представление действительно появляется на экране (во время прокрутки). Это именно то поведение, которое мы хотим.

Для любых других компонентов (например, HStack, VStack, ScrollView) все представления будут отображаться на экране, как только отобразится родительское представление. Даже если вам нужно глубоко прокрутить содержимое, тело загрузится и будет вызван .onAppear (). Он начнет загружать все ваши изображения, как только родительский вид появится на экране. Хуже того, в случае ScrollView порядок вызовов .onAppear () будет вызываться из представления, самого дальнего в иерархии, к первому. Другими словами, изображения внизу или в конце вашего представления загрузятся первыми.

В Xcode Beta 3 нет обходного пути, но я надеюсь, что Apple внесет в него некоторые изменения. Возможно, мы получим то же поведение, что и List в ScrollView, или, может быть, мы получим своего рода HList / VList или компонент aGrid, который будет ставить в очередь и повторно использовать представления в своем теле. Или, может быть, они вызовут .onAppear () в правильном порядке.

Надеюсь, вам понравилась эта статья, и я здесь, если у вас есть какие-либо вопросы или отзывы.