PhotosUI для Swift: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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 для плавного скролла коллекций.

Рекомендаций