PhotosUI для Swift: Полное руководство по выбору фото и видео
1. Что такое PhotosUI и зачем она нужна
PhotosUI — это фреймворк от Apple, предоставляющий готовые пользовательские интерфейсы для работы с медиатекой устройства. Он входит в состав UIKit и является надстройкой над более низкоуровневым фреймворком Photos. Основная задача PhotosUI — дать разработчику возможность быстро и стандартизированно реализовать функционал выбора фотографий и видео, не углубляясь в сложную логику управления доступом, навигации по альбомам и отображения превью.
Зачем это нужно? Во-первых, это экономит время. Вместо того чтобы писать собственный браузер медиатеки с нуля, вы получаете готовый, отполированный интерфейс, который пользователи уже знают. Во-вторых, это безопасность и соответствие гайдлайнам Apple. PhotosUI автоматически обрабатывает запросы на доступ к библиотеке (через PHPhotoLibrary) и корректно работает с новыми версиями iOS, включая поддержку приватности и ограниченного доступа. В-третьих, это гибкость: вы можете настроить внешний вид, разрешить множественный выбор, фильтровать только фото или только видео, и даже конфигурировать поведение при редактировании.
Основные сценарии использования: аватарки в профиле, прикрепление изображений к сообщениям, загрузка видео в социальные сети, создание коллажей, интеграция с фильтрами и редакторами. PhotosUI идеально подходит для любого приложения, которому требуется доступ к пользовательской медиатеке.
2. Установка
PhotosUI является частью стандартной библиотеки iOS SDK. Для его использования не требуется установка через CocoaPods, Swift Package Manager или Carthage. Всё, что нужно — это импортировать фреймворк в ваш проект.
Важно: PhotosUI доступен начиная с iOS 8.0. Однако для использования всех современных возможностей (например, PHPickerViewController) рекомендуется iOS 14+.
// Импорт фреймворка
import PhotosUI
Никаких дополнительных команд в терминале не требуется. Просто добавьте import PhotosUI в файл, где вы планируете использовать функционал выбора медиа.
3. Быстрый старт: минимальный рабочий пример
Самый простой способ показать пользователю интерфейс выбора фото — использовать PHPickerViewController (iOS 14+). Этот контроллер работает в отдельном процессе, что повышает производительность и безопасность.
Пример: кнопка на экране, при нажатии на которую открывается пикер.
import UIKit
import PhotosUI
class ViewController: UIViewController, PHPickerViewControllerDelegate {
@IBAction func selectPhotoTapped(_ sender: UIButton) {
// 1. Создаем конфигурацию пикера
var config = PHPickerConfiguration()
config.selectionLimit = 1 // Одно фото
config.filter = .images // Только изображения
// 2. Создаем контроллер
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
// 3. Показываем
present(picker, animated: true)
}
// MARK: - PHPickerViewControllerDelegate
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
guard let provider = results.first?.itemProvider else { return }
// 4. Загружаем UIImage
if provider.canLoadObject(ofClass: UIImage.self) {
provider.loadObject(ofClass: UIImage.self) { image, error in
DispatchQueue.main.async {
// Используем полученное изображение
print("Изображение загружено: \(String(describing: image))")
}
}
}
}
}
Этот код создает пикер, который позволяет выбрать ровно одно изображение. После выбора делегат получает результат, и мы загружаем его как UIImage.
4. Основные классы и методы
Рассмотрим ключевые компоненты PhotosUI. Для каждого приведена сигнатура, краткое описание и рабочий пример.
4.1 PHPickerConfiguration
Сигнатура: class PHPickerConfiguration
Назначение: Конфигурация для PHPickerViewController. Позволяет задать лимит выбора, тип медиа (фото/видео), а также режим выбора (по умолчанию — обычный).
Пример:
var config = PHPickerConfiguration()
config.selectionLimit = 0 // 0 означает безлимитный выбор
config.filter = .videos // Только видео
config.preferredAssetRepresentationMode = .current // Использовать текущую версию
4.2 PHPickerViewController
Сигнатура: class PHPickerViewController : UIViewController
Назначение: Основной контроллер для выбора медиа. Представляет собой системный интерфейс, работающий в отдельном процессе.
Пример:
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
present(picker, animated: true)
4.3 PHPickerViewControllerDelegate
Сигнатура: protocol PHPickerViewControllerDelegate : AnyObject
Назначение: Протокол для получения результатов выбора. Содержит единственный обязательный метод picker(_:didFinishPicking:).
Пример:
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
for result in results {
print("Выбран элемент: \(result.assetIdentifier ?? "unknown")")
}
}
4.4 PHPickerResult
Сигнатура: class PHPickerResult
Назначение: Представляет собой результат выбора одного элемента. Содержит itemProvider (для загрузки данных) и assetIdentifier (локальный идентификатор в медиатеке).
Пример:
let result: PHPickerResult = results.first!
let provider = result.itemProvider
if provider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
// Это видео
}
4.5 NSItemProvider (из PHPickerResult)
Сигнатура: class NSItemProvider
Назначение: Провайдер данных, через который можно загрузить само изображение, видео или данные. Ключевые методы: canLoadObject(ofClass:) и loadObject(ofClass:completionHandler:).
Пример загрузки UIImage:
if provider.canLoadObject(ofClass: UIImage.self) {
provider.loadObject(ofClass: UIImage.self) { (image, error) in
// image - это UIImage?
}
}
Пример загрузки Data (для видео):
provider.loadDataRepresentation(forTypeIdentifier: UTType.movie.identifier) { data, error in
// data - это Data?
}
4.6 PHPhotoLibrary (для запроса доступа)
Сигнатура: class PHPhotoLibrary
Назначение: Хотя PHPicker не требует явного запроса доступа (он работает в изолированном процессе), если вы хотите сохранять медиа или получать доступ к полной библиотеке, вам понадобится этот класс. Метод requestAuthorization(for:handler:).
Пример:
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
switch status {
case .authorized:
print("Полный доступ")
case .limited:
print("Ограниченный доступ")
default:
print("Доступ запрещен")
}
}
4.7 PHAsset (для работы с медиатекой)
Сигнатура: class PHAsset
Назначение: Представляет отдельный медиа-объект (фото, видео, аудио) в библиотеке Photos. Используется вместе с PHImageManager для загрузки изображений по запросу.
Пример (получение asset по идентификатору):
let assetId = result.assetIdentifier
let assets = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: nil)
if let asset = assets.firstObject {
print("Asset найден: \(asset.localIdentifier)")
}
4.8 PHImageManager (загрузка изображений)
Сигнатура: class PHImageManager
Назначение: Менеджер для запроса изображений и видео из библиотеки Photos. Позволяет загружать изображения в нужном размере, с кэшированием и без.
Пример (загрузка изображения по asset):
let manager = PHImageManager.default()
let options = PHImageRequestOptions()
options.deliveryMode = .highQualityFormat
options.isSynchronous = false
manager.requestImage(for: asset,
targetSize: CGSize(width: 200, height: 200),
contentMode: .aspectFill,
options: options) { image, info in
// image - это UIImage?
}
4.9 PHImageRequestOptions
Сигнатура: class PHImageRequestOptions
Назначение: Настройки для запроса изображения через PHImageManager. Позволяет задать качество, синхронность, сетевой доступ и версию изображения.
Пример:
let options = PHImageRequestOptions()
options.isNetworkAccessAllowed = true // Разрешить загрузку из iCloud
options.deliveryMode = .opportunistic // Сначала быстрое превью, потом полное
options.version = .current
5. Реальный практический пример (150+ строк кода)
Создадим экран, который позволяет выбрать несколько фотографий, отобразить их в коллекции и сохранить одну из них в фотоальбом.
import UIKit
import PhotosUI
import Photos
class MediaPickerViewController: UIViewController, PHPickerViewControllerDelegate, UICollectionViewDataSource {
@IBOutlet weak var collectionView: UICollectionView!
var selectedImages: [UIImage] = []
@IBAction func openPicker(_ sender: UIButton) {
var config = PHPickerConfiguration()
config.selectionLimit = 5 // Максимум 5 фото
config.filter = .images
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
present(picker, animated: true)
}
// MARK: - PHPickerDelegate
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
let group = DispatchGroup()
var loadedImages: [UIImage] = []
for result in results {
group.enter()
let provider = result.itemProvider
if provider.canLoadObject(ofClass: UIImage.self) {
provider.loadObject(ofClass: UIImage.self) { image, error in
if let img = image as? UIImage {
loadedImages.append(img)
}
group.leave()
}
} else {
group.leave()
}
}
group.notify(queue: .main) {
self.selectedImages = loadedImages
self.collectionView.reloadData()
}
}
// MARK: - Сохранение изображения
@IBAction func saveFirstImage(_ sender: UIButton) {
guard let imageToSave = selectedImages.first else { return }
PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in
guard status == .authorized else {
print("Нет прав на сохранение")
return
}
PHPhotoLibrary.shared().performChanges {
PHAssetChangeRequest.creationRequestForAsset(from: imageToSave)
} completionHandler: { success, error in
DispatchQueue.main.async {
if success {
print("Изображение сохранено!")
} else {
print("Ошибка сохранения: \(error?.localizedDescription ?? "")")
}
}
}
}
}
// MARK: - UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return selectedImages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath)
let imageView = cell.viewWithTag(100) as? UIImageView
imageView?.image = selectedImages[indexPath.row]
return cell
}
}
В этом примере мы используем DispatchGroup для синхронизации загрузки нескольких изображений, после чего обновляем коллекцию. Кнопка сохранения демонстрирует работу с PHPhotoLibrary для записи в альбом.
6. Советы по производительности и типичные ошибки
Производительность
- Используйте PHPicker вместо UIImagePickerController: PHPicker работает в отдельном процессе, не нагружая ваше приложение. Он также автоматически обрабатывает большие библиотеки.
- Загружайте изображения асинхронно: Всегда используйте
loadObject(ofClass:completionHandler:)илиloadDataRepresentation. Никогда не блокируйте главный поток. - Кэшируйте превью: Если вы работаете с
PHImageManagerнапрямую, используйтеPHCachingImageManagerдля плавного скролла коллекций.
Также в библиотеке
Работа с CSV в Go (Golang): полное руководство по парсингу и записи
Тестирование в Java: фреймворки и инструменты (JUnit, TestNG, Mockito)
Кэширование в Swift (iOS/macOS): Redis, NSCache и другие решения
Конфигурация приложений Java: библиотеки и инструменты
WebSocket библиотека Swift: Starscream для iOS/macOS