Воспользуйтесь простотой документации Tenor API для создания ячеек воспроизведения GIF.
Tenor, популярная поисковая машина GIF, является отличной конечной точкой API, с помощью которой можно продемонстрировать, как реализовать классы горизонтальной и вертикальной прокрутки Swift с встроенной функциональностьюAVFoundation
в Objective-C!
Благодаря обширной документации, краткому руководству по началу работы, передовым практикам и подробным curl
командам Tenor предлагает широкие возможности для разработки приложений для iOS.
Это также отлично подходит для тех, кто забывает о сложности интеграции API для простоты SDK!
Что вы добьетесь
В этом блоге вы воспользуетесь элементарной простотой документации Tenor API для создания ячеек воспроизведения GIF с помощью класса UICollectionViewCells
Swift.
В частности, вы покажете следующее:
- Знание как Swift, так и Objective-C, где класс представления находится в Swift, а класс видеоплеера - в Objective-C.
- Родной iOS UIKit (раскадровка, автоматический макет и классы размеров).
- Надлежащие сторонние сети или фреймворки для кэширования изображений, например _4 _ / _ 5_.
Показывая эти вещи, вы добьетесь следующего:
- Получите данные конечной точки, отобразите их в виде коллекции.
- Панель поиска, которая обновляет представление коллекции по мере ввода пользователем.
- При нажатии на ячейку представления коллекции приложение начинает воспроизведение видео.
- Правильное отображение на iPhone и iPad во всех четырех ориентациях.
- Модульные тесты.
Настраивать
Откройте шаблон приложения с одним представлением в Xcode. Перейдите в корневой каталог через терминал интерфейса командной строки.
В корневом каталоге напишите pod init
. После инициализации модуля добавьте Alamofire
и SDWebImage
в свой подфайл и запустите pod install
.
Вот и все! Все готово.
Получение данных конечной точки
Следующим шагом является получение данных конечной точки. По мере того, как вы создаете свое приложение в соответствии с представлением модели, представлением структуры модели, вы создадите четыре structs
, каждое из которых соответствует новому протоколу Apple codable
.
Первая структура - это структура GIF.
GIF struct
В своем GIF struct
объявите следующие константы: tags: [String]?
, url: URL?
, media: [MediaCollection]?
, created: Double?
, shares: Int?
, itemURL: URL?
, hasAudio: Bool?
, title: String?
, id: String?
и ключи кодирования.
Для наших ключей кодирования вы будете использовать enum
с регистрами:
enum CodingKeys: String, CodingKey { case tags, url, media, created, shares case itemURL = "itemURL" case hasAudio = "has audio" case title, id }
MediaCollection struct
Ваш следующий struct
называется MediaCollection
. Это намного проще, чем другая структура, поскольку она просто декодирует различные типы мультимедиа в тип Swift.
В вашем MediaCollection
struct
объявите следующие константы: nanoMP4
, nanoWebM
, tinyGIF
, tinyMP4
, tinyWebM
, webM
, gif
, mp4
, loopedMP4
, mediumGIF
и nanoGIF
, как необязательный тип Media
.
Для наших ключей кодирования вы будете использовать enum
с регистрами, предоставляя значения String для всех, кроме gif
или mp4
, которые очевидны.
СМИ struct
Ваша следующая структура называется Media
.
В своей структуре Media
объявите следующие константы: url: URL?
, dimension: [Int]?
, duration: Double?
, preview: URL?
, size: Int64?
.
Для ключей кодирования перечислите случаи без значений для всех, кроме dimension
, значение которого вы установите равным dims
.
Ответ struct
И последнее, но не менее важное - это ваша структура Response
. Вы объявите эту структуру в общем виде, чтобы Response
можно было декодировать тогда и только тогда, когда она соответствует декодируемому протоколу.
Объявите константы для webURL:URL?
, results: T?
и next: String?
.
Для ключей кодирования перечислите случаи для всех констант, кроме webURL
, значение которого вы установите равным weburl
.
Потрясающие! Вы создали свои структуры. Это первый шаг к получению данных конечной точки. Следующим шагом является настройка вызовов конечных точек API.
Конечные точки
Создайте struct
для URLManager
, который будет управлять методами convertItems
, getURL
, getAuthenticationParameters
, getLimitingParameters
и getAnonymousIdParameters
.
Запрограммируйте convertItems
как частную константу:
static private let convertItems: ((Parameters) -> [URLQueryItem]) = { parameters in return parameters.map { return URLQueryItem(name: $0, value: $1) } }
Программа ТЗ getURL
как общедоступный метод:
Программа getAuthenticationParameters
как частный метод:
static private func getAuthenticationParameters() -> Parameters { return ["key" : Configuration.key] }
Программа getAnonymousIdParameters
как частный метод:
static private func getAnonymousIdParameters() -> Parameters? { guard let anonymoudId = UserDefaults.standard.string(forKey: kAnonymousIdKey) else { return nil } return ["anon_id" : anonymoudId] }
Программа getLimitingParameters
как частный метод:
static private func getLimitingParameters() -> Parameters { return ["limit" : "\(Configuration.pageLimit)"] }
И последнее, но не менее важное: в этом списке нужно запрограммировать enum
с парой случаев:
import Foundation enum EndPoint: String { case anonymousId = "/v1/anonid" case search = "/v1/search" }
Вам нужно добавить еще два файла: один с именем Alias.swift
, а другой с именем Constants.swift
. В первом запрограммируйте typealias
как:
typealias Parameters = [String:String]
Во втором запрограммируйте константу kAnonymousIdKey
как AnonymousId
. Сгруппируйте эти последние два файла в одну папку под названием Helper.
Панель поиска
Наша архитектура для реализации панели поиска состоит из двух частей: во-первых, это контроллер представления с именем SearchVC.swift
, во-вторых, расширение категории с именем SearchVC+CollectionView.swift
.
Добавьте файл с именем SearchVC.swift
. Внутри файла импортируйте:
import UIKit import Alamofire import AlamofireImage
Создать торговые точки:
@IBOutlet weak var searchBar: UISearchBar! @IBOutlet weak var resultCollectionView: UICollectionView!
Создайте свойства для данных:
// MARK: - Data internal lazy var resultsArray: [GIF] = [] internal let cellIdentifier = "PreviewCell" internal let cellHeight: CGFloat = 250 private let anonIdViewModel = AnonIdViewModel() private let searchViewModel = SearchViewModel()
Звоните fetchAnonymousId()
в viewDidLoad()
.
Создайте функцию для получения анонимных идентификаторов:
private func fetchAnonymousId() { ActivityIndicator.startAnimating() anonIdViewModel.getAnonymousId { [weak self] success in ActivityIndicator.stopAnimating() if success { self?.fetchResult() } else { self?.showRetryAlert() } } }
Если наш вызов будет успешным, вы получите результаты в функции с именем fetchResult()
.
private func fetchResult(for keyword: String = "") { ActivityIndicator.startAnimating() searchViewModel.search(using: keyword) { [weak self] (data, error) in DispatchQueue.main.async { if let error = error { // if canceled, do not show alert guard (error as NSError).code != -999 else { return } self?.showErrorAlert(with: error.localizedDescription) } else if let data = data { self?.resultsArray = data self?.resultCollectionView.reloadData() } ActivityIndicator.stopAnimating() } } }
Запрограммируйте вызов предупреждения об ошибке:
private func showErrorAlert(with message: String) { let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .alert) let okayAction = UIAlertAction(title: "Okay", style: .default) alertController.addAction(okayAction) present(alertController, animated: true, completion: nil) }}
Если вызов не удался, запрограммируйте функцию с именем showRetryAlert()
:
private func showRetryAlert() { let alertController = UIAlertController(title: "Error", message: "Unable to fetch anonymous id.", preferredStyle: .alert) let retryAction = UIAlertAction(title: "Retry", style: .default) { [weak self] _ in self?.fetchAnonymousId() } alertController.addAction(retryAction) let withoutAction = UIAlertAction(title: "Search without anonymous id.", style: .default) { [weak self] _ in self?.fetchResult() } alertController.addAction(withoutAction) present(alertController, animated: true, completion: nil) }
Запрограммируйте изменение ориентации:
@objc func orientationDidChange() { resultCollectionView.collectionViewLayout.invalidateLayout() }
Кроме того, вам необходимо запрограммировать обновление коллекции признаков:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { resultCollectionView.collectionViewLayout.invalidateLayout() }
Наблюдатель за ориентациями
Если пользователь меняет ориентацию, приложение должно соответствующим образом обновить пользовательский интерфейс для представления коллекции. Для этого приложение должно получать уведомление от наблюдателя.
Наблюдатели наблюдают за изменениями в состоянии. Если состояние ориентации изменяется, наблюдатель получает уведомление, которое может вызвать изменение кода. Оба файла должны добавлять и удалять наблюдателей для уведомлений об изменениях ориентации:
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) NotificationCenter.default.addObserver(self, selector: #selector(SearchVC.orientationDidChange), name: Notification.Name.UIDeviceOrientationDidChange, object: nil) } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) NotificationCenter.default.removeObserver(self) }
Запрограммируйте UISearchBarDelegate
Ранее вы добавляли экземпляр UISearchBar
в качестве розетки.
Однако экземпляры UISearchBar
должны делегировать свои функции контроллеру, в котором они объявлены.
Вот как расширить функциональные возможности контроллера представления поиска для обработки функциональности панели поиска:
extension SearchVC: UISearchBarDelegate { func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { fetchResult(for: searchText) }
Этот код добавляется в нижнюю часть контроллера панели поиска.
Запрограммируйте SearchVC + CollectionView.swift
SearchVC+CollectionView.swift
является расширением SearchVC
, но в отдельном файле, чтобы код представления коллекции контроллера представления был хорошо организован (т. Е. Разделение задач).
Первая часть - принять протокол UICollectionViewDataSource
, реализовав необходимые методы для количества элементов в каждом разделе и ячеек для каждого элемента в этих разделах:
extension SearchVC: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return resultsArray.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as? PreviewCell if let url = resultsArray[indexPath.row].media?.first?.mp4?.preview { cell?.thumbImageView.af_setImage(withURL: url, placeholderImage: imageLiteral(resourceName: "placeholder"),imageTransition: .crossDissolve(0.1)) } return cell ?? UICollectionViewCell() } }
Следующим шагом является реализация делегата представления коллекции:
extension SearchVC: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let url = resultsArray[indexPath.row].media?.first?.mp4?.url else { return } let _ = VideoPlayer.playVideo(with: url) } }
Последний шаг - реализовать макет потока для делегата представления коллекции.
extension SearchVC: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { switch UIDevice.current.userInterfaceIdiom { case .carPlay, .tv, .unspecified: fallthrough case .phone: return CGSize(width: collectionView.frame.width, height: cellHeight) case .pad: return CGSize(width: collectionView.frame.width/2, height: cellHeight) } } }
Воспроизведение видео
Вы измените язык для реализации видеоплеера.
AVFoundation
- это собственный SDK Apple для воспроизведения видео, но он написан на Objective-C. Это не написано на Swift.
Хотя мы можем реализовать это в Swift, нет никаких преимуществ, поскольку реализация в Objective-C ближе к ядру AVFoundation
, чем Swift.
Создайте два файла Objective-C: один VideoPlayer.h
, другой VideoPlayer.m
.
В заголовочный файл добавьте следующее:
//// VideoPlayer.h // Tenorize //// Created by Eric Giannini on 8/29/18. // Copyright © 2018 Eric Giannini. All rights reserved. // #import <AVKit/AVKit.h> #import <Foundation/Foundation.h> @interface VideoPlayer : NSObject +(nonnull AVPlayerViewController *)playVideoWithURL:(NSURL *_Nonnull)url; @end
Вы объявляете метод в файле заголовка, но фактически программируете этот метод в файле реализации.
Вот:
#import "VideoPlayer.h" #import <Foundation/Foundation.h> @implementation VideoPlayer +(AVPlayerViewController *)playVideoWithURL:(NSURL *)url { AVPlayer *player = [[AVPlayer alloc] initWithURL:url]; AVPlayerViewController *playerVC = [[AVPlayerViewController alloc] init]; [playerVC setPlayer:player]; UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; [[window rootViewController] presentViewController:playerVC animated:YES completion:^{ [player play]; }]; return playerVC; } @end
Ваш метод вызывается с URL-адресом (т. Е. URL-адресом GIF Tenor API).
Экземпляр AVPlayer
одновременно инициализируется и выделяется ресурсы. Экземпляр AVPlayerViewController
инициализируется и выделяет ресурсы.
Затем экземпляр AVPlayer
устанавливается как проигрыватель для экземпляра AVPlayerViewController
. Контроллер представления установлен как игрок.
Синопсис
Вы получаете данные из конечной точки API с помощью Alamofire
, отображая их в представлении коллекции с ячейками представления коллекции с помощью SDWebImage
.
Вы создаете панель поиска, которая обновляет представление коллекции по мере того, как пользователь вводит новые ячейки. Вы разрешаете касание ячейки представления коллекции, чтобы воспроизводились файлы GIF.
Вы создаете приложение, чтобы оно правильно отображалось на iPhone и iPad во всех четырех ориентациях, изначально с iOS UIKit (т.е. раскадровка, автоматическая компоновка и классы размера). Вы создаете модульные тесты.
- Панель поиска, которая обновляет представление коллекции по мере ввода пользователем.
- При нажатии на ячейку представления коллекции приложение начинает воспроизведение видео.
- Правильно отображается на iPhone и iPad во всех четырех ориентациях.
- Функциональность модульных тестов
Кодовая база
Если вы хотите ознакомиться с исходным кодом полнофункционального приложения, скачайте его.