Media Picker
To infinity and beyond!
Андрей Юткин
Demo
UIImagePickerController
3
UIImagePickerController
либо камера, либо галерея
3
UIImagePickerController
либо камера, либо галерея
должен быть представлен модально
3
UIImagePickerController
либо камера, либо галерея
должен быть представлен модально
может крэшиться до вызова метода
делегата
3
Камера: готовые решения
MWPhotoBrowser
DBCamera
FastttCamera
LLSimpleCamera
SKFCamera
ALCameraViewController
TGCameraViewController
MMSCameraViewController
…
4
AVFoundation
5
AVFoundation
5
AVCaptureSession
AVFoundation
5
AVCaptureSession
AVCaptureDeviceInput
AVCaptureDevice
(Camera)
AVFoundation
5
AVCaptureSession
AVCaptureStillImageOutput AVCaptureMovieFileOutput
AVCaptureDeviceInput
AVCaptureDevice
(Camera)
AVCaptureConnection AVCaptureConnection
AVFoundation
5
AVCaptureSession
AVCaptureStillImageOutput AVCaptureMovieFileOutput
AVCaptureDeviceInput
AVCaptureDevice
(Camera)
Настройка AVCaptureSession
6
Настройка AVCaptureSession
let videoDevices =

AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo)
let backCamera = videoDevices?.first { $0.position == .back }
let input = try AVCaptureDeviceInput(device: backCamera)
6
Настройка AVCaptureSession
let videoDevices =

AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo)
let backCamera = videoDevices?.first { $0.position == .back }
let input = try AVCaptureDeviceInput(device: backCamera)
let output = AVCaptureStillImageOutput()

output.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG]
6
Настройка AVCaptureSession
let captureSession = AVCaptureSession()

captureSession.sessionPreset = AVCaptureSessionPresetPhoto
if captureSession.canAddInput(input) {

captureSession.addInput(input)

}
if captureSession.canAddOutput(output) {

captureSession.addOutput(output)

}
captureSession.startRunning()
7
Настройка AVCaptureSession
let captureSession = AVCaptureSession()

captureSession.sessionPreset = AVCaptureSessionPresetPhoto
if captureSession.canAddInput(input) {

captureSession.addInput(input)

}
if captureSession.canAddOutput(output) {

captureSession.addOutput(output)

}
captureSession.startRunning()
7
Настройка AVCaptureSession
let captureSession = AVCaptureSession()

captureSession.sessionPreset = AVCaptureSessionPresetPhoto
if captureSession.canAddInput(input) {

captureSession.addInput(input)

}
if captureSession.canAddOutput(output) {

captureSession.addOutput(output)

}
captureSession.startRunning()
7
Настройка AVCaptureSession
let captureSession = AVCaptureSession()

captureSession.sessionPreset = AVCaptureSessionPresetPhoto
if captureSession.canAddInput(input) {

captureSession.addInput(input)

}
if captureSession.canAddOutput(output) {

captureSession.addOutput(output)

}
captureSession.startRunning()
7
Настройка AVCaptureSession
let captureSession = AVCaptureSession()

captureSession.sessionPreset = AVCaptureSessionPresetPhoto
if captureSession.canAddInput(input) {

captureSession.addInput(input)

}
if captureSession.canAddOutput(output) {

captureSession.addOutput(output)

}
captureSession.startRunning()
7
В фоновом потоке!
Отображение превью
let layer = AVCaptureVideoPreviewLayer(session: captureSession)
cameraOutputView.layer.addSublayer(layer)
8
Отображение превью
let layer = AVCaptureVideoPreviewLayer(session: captureSession)
cameraOutputView.layer.addSublayer(layer)
9
Отображение превью
let layer = AVCaptureVideoPreviewLayer(session: captureSession)
cameraOutputView.layer.addSublayer(layer)
9
1 AVCaptureSession = 1 AVCaptureVideoPreviewLayer
Отображение превью
let layer = AVCaptureVideoPreviewLayer(session: captureSession)
cameraOutputView.layer.addSublayer(layer)
9
Несколько AVCaptureSession не могут работать одновременно
1 AVCaptureSession = 1 AVCaptureVideoPreviewLayer
AVCaptureStillImageOutput
Несколько превью
10
AVCaptureSession
AVCaptureStillImageOutput AVCaptureVideoDataOutput
Несколько превью
10
AVCaptureSession
AVCaptureVideoDataOutputSampleBufferDelegate
captureOutput(_:didOutputSampleBuffer:from:)
AVCaptureStillImageOutput AVCaptureVideoDataOutput
Несколько превью
10
AVCaptureSession
Рендеринг CMSampleBuffer
11
UIView
CMSampleBuffer
iPhone 5s и выше
Рендеринг CMSampleBuffer
11
UIView
CMSampleBuffer
AVCaptureVideoDataOutputSampleBufferDelegate
func captureOutput(
_: AVCaptureOutput?,
didOutputSampleBuffer sampleBuffer: CMSampleBuffer?,
from _: AVCaptureConnection?)
{
let imageBuffer: CVImageBuffer? =
sampleBuffer.flatMap { CMSampleBufferGetImageBuffer($0) }
if let imageBuffer = imageBuffer, !isInBackground {
views.forEach { $0.imageBuffer = imageBuffer }
}
}
var views = [GLKViewSubclass]()
12
AVCaptureVideoDataOutputSampleBufferDelegate
func captureOutput(
_: AVCaptureOutput?,
didOutputSampleBuffer sampleBuffer: CMSampleBuffer?,
from _: AVCaptureConnection?)
{
let imageBuffer: CVImageBuffer? =
sampleBuffer.flatMap { CMSampleBufferGetImageBuffer($0) }
if let imageBuffer = imageBuffer, !isInBackground {
views.forEach { $0.imageBuffer = imageBuffer }
}
}
var views = [GLKViewSubclass]()
12
AVCaptureVideoDataOutputSampleBufferDelegate
func captureOutput(
_: AVCaptureOutput?,
didOutputSampleBuffer sampleBuffer: CMSampleBuffer?,
from _: AVCaptureConnection?)
{
let imageBuffer: CVImageBuffer? =
sampleBuffer.flatMap { CMSampleBufferGetImageBuffer($0) }
if let imageBuffer = imageBuffer, !isInBackground {
views.forEach { $0.imageBuffer = imageBuffer }
}
}
var views = [GLKViewSubclass]()
12
AVCaptureVideoDataOutputSampleBufferDelegate
func captureOutput(
_: AVCaptureOutput?,
didOutputSampleBuffer sampleBuffer: CMSampleBuffer?,
from _: AVCaptureConnection?)
{
let imageBuffer: CVImageBuffer? =
sampleBuffer.flatMap { CMSampleBufferGetImageBuffer($0) }
if let imageBuffer = imageBuffer, !isInBackground {
views.forEach { $0.imageBuffer = imageBuffer }
}
}
var views = [GLKViewSubclass]()
12
GLKViewSubclass
// instance vars:
let eaglContext = EAGLContext(api: .openGLES2)
let ciContext = CIContext(eaglContext: eaglContext)
var imageBuffer: CVImageBuffer?
// draw(_:) implementation:
if let imageBuffer = imageBuffer {
let image = CIImage(cvPixelBuffer: imageBuffer)
ciContext.draw(
image,
in: drawableBounds(for: rect),
from: sourceRect(of: image, targeting: rect)
)
}
13
GLKViewSubclass
// instance vars:
let eaglContext = EAGLContext(api: .openGLES2)
let ciContext = CIContext(eaglContext: eaglContext)
var imageBuffer: CVImageBuffer?
// draw(_:) implementation:
if let imageBuffer = imageBuffer {
let image = CIImage(cvPixelBuffer: imageBuffer)
ciContext.draw(
image,
in: drawableBounds(for: rect),
from: sourceRect(of: image, targeting: rect)
)
}
13
GLKViewSubclass
// instance vars:
let eaglContext = EAGLContext(api: .openGLES2)
let ciContext = CIContext(eaglContext: eaglContext)
var imageBuffer: CVImageBuffer?
// draw(_:) implementation:
if let imageBuffer = imageBuffer {
let image = CIImage(cvPixelBuffer: imageBuffer)
ciContext.draw(
image,
in: drawableBounds(for: rect),
from: sourceRect(of: image, targeting: rect)
)
}
13
GLKViewSubclass
// instance vars:
let eaglContext = EAGLContext(api: .openGLES2)
let ciContext = CIContext(eaglContext: eaglContext)
var imageBuffer: CVImageBuffer?
// draw(_:) implementation:
if let imageBuffer = imageBuffer {
let image = CIImage(cvPixelBuffer: imageBuffer)
ciContext.draw(
image,
in: drawableBounds(for: rect),
from: sourceRect(of: image, targeting: rect)
)
}
13
GLKViewSubclass
// instance vars:
let eaglContext = EAGLContext(api: .openGLES2)
let ciContext = CIContext(eaglContext: eaglContext)
var imageBuffer: CVImageBuffer?
// draw(_:) implementation:
if let imageBuffer = imageBuffer {
let image = CIImage(cvPixelBuffer: imageBuffer)
ciContext.draw(
image,
in: drawableBounds(for: rect),
from: sourceRect(of: image, targeting: rect)
)
}
13
GLKViewSubclass
// instance vars:
let eaglContext = EAGLContext(api: .openGLES2)
let ciContext = CIContext(eaglContext: eaglContext)
var imageBuffer: CVImageBuffer?
// draw(_:) implementation:
if let imageBuffer = imageBuffer {
let image = CIImage(cvPixelBuffer: imageBuffer)
ciContext.draw(
image,
in: drawableBounds(for: rect),
from: sourceRect(of: image, targeting: rect)
)
}
13
GLKViewSubclass
// instance vars:
let eaglContext = EAGLContext(api: .openGLES2)
let ciContext = CIContext(eaglContext: eaglContext)
var imageBuffer: CVImageBuffer?
// draw(_:) implementation:
if let imageBuffer = imageBuffer {
let image = CIImage(cvPixelBuffer: imageBuffer)
ciContext.draw(
image,
in: drawableBounds(for: rect),
from: sourceRect(of: image, targeting: rect)
)
}
13
GLKViewSubclass
// instance vars:
let eaglContext = EAGLContext(api: .openGLES2)
let ciContext = CIContext(eaglContext: eaglContext)
var imageBuffer: CVImageBuffer?
// draw(_:) implementation:
if let imageBuffer = imageBuffer {
let image = CIImage(cvPixelBuffer: imageBuffer)
ciContext.draw(
image,
in: drawableBounds(for: rect),
from: sourceRect(of: image, targeting: rect)
)
}
13
OpenGL и background
back in AVCaptureVideoDataOutputSampleBufferDelegate
14
OpenGL и background
back in AVCaptureVideoDataOutputSampleBufferDelegate
// UIApplicationWillResignActive
func handleAppWillResignActive(_: NSNotification) {
captureOutputDelegateBackgroundQueue.sync {
glFinish()
self.isInBackground = true
}
}
// UIApplicationDidBecomeActive
func handleAppDidBecomeActive(_: NSNotification) {
captureOutputDelegateBackgroundQueue.async {
self.isInBackground = false
}
}
14
OpenGL и background
back in AVCaptureVideoDataOutputSampleBufferDelegate
// UIApplicationWillResignActive
func handleAppWillResignActive(_: NSNotification) {
captureOutputDelegateBackgroundQueue.sync {
glFinish()
self.isInBackground = true
}
}
// UIApplicationDidBecomeActive
func handleAppDidBecomeActive(_: NSNotification) {
captureOutputDelegateBackgroundQueue.async {
self.isInBackground = false
}
}
14
OpenGL и background
back in AVCaptureVideoDataOutputSampleBufferDelegate
// UIApplicationWillResignActive
func handleAppWillResignActive(_: NSNotification) {
captureOutputDelegateBackgroundQueue.sync {
glFinish()
self.isInBackground = true
}
}
// UIApplicationDidBecomeActive
func handleAppDidBecomeActive(_: NSNotification) {
captureOutputDelegateBackgroundQueue.async {
self.isInBackground = false
}
}
14
Summary: превью камеры
15
Summary: превью камеры
15
AVCaptureVideoDataOutput
AVCaptureSession
Output Delegate
Summary: превью камеры
15
AVCaptureVideoDataOutput
AVCaptureSession
Output Delegate
Summary: превью камеры
15
AVCaptureVideoDataOutput
AVCaptureSession
Output Delegate
Summary: превью камеры
15
AVCaptureVideoDataOutput
AVCaptureSession
Источники фотографий
16
Источники фотографий
файловая система
16
Источники фотографий
файловая система
пользовательская галерея
16
Источники фотографий
файловая система
пользовательская галерея
сеть
16
Что нам нужно от фото?
17
Что нам нужно от фото?
отобразить в UI
17
Что нам нужно от фото?
отобразить в UI
получить оригинал
17
Что нам нужно от фото?
отобразить в UI
получить оригинал
узнать размер
17
Что нам нужно от фото?
отобразить в UI
получить оригинал
узнать размер
отменить загрузку
17
Что нам нужно от фото?
отобразить в UI
получить оригинал
узнать размер
отменить загрузку
18
Что нам нужно от фото?
отобразить в UI
получить оригинал
узнать размер
отменить загрузку
18
— асинхронно
Отображение в UI
19
Отображение в UI
let viewSize: CGSize

let contentMode: ContentMode // enum: aspectFit/aspectFill

19
Отображение в UI
let viewSize: CGSize

let contentMode: ContentMode // enum: aspectFit/aspectFill

let handler = { (image: UIImage?) in

imageView.image = image

}

19
Отображение в UI
let viewSize: CGSize

let contentMode: ContentMode // enum: aspectFit/aspectFill

let handler = { (image: UIImage?) in

imageView.image = image

}

let deliveryMode: DeliveryMode // enum: progressive/best
19
Отображение в UI
func requestImage(
20
viewSize: CGSize,
contentMode: ContentMode,
deliveryMode: DeliveryMode,
handler: @escaping (UIImage?) -> ())
Отображение в UI
func requestImage(
20
handler: @escaping (UIImage?) -> ())
options: ImageRequestOptions,
struct ImageRequestOptions {
let viewSize: CGSize
let contentMode: ContentMode
let deliveryMode: DeliveryMode
}
Отображение в UI
protocol InitializableWithCGImage {

init(cgImage: CGImage)

}

21
Отображение в UI
protocol InitializableWithCGImage {

init(cgImage: CGImage)

}

extension UIImage: InitializableWithCGImage {}

extension NSImage: InitializableWithCGImage {}
21
Отображение в UI
func requestImage<T: InitializableWithCGImage>(
options: ImageRequestOptions,
handler: @escaping (
22
) -> ())T?
Отображение в UI
func requestImage<T: InitializableWithCGImage>(
options: ImageRequestOptions,
handler: @escaping (
22
-> ImageRequestId
) -> ())T?
Отображение в UI
func requestImage<T: InitializableWithCGImage>(
options: ImageRequestOptions,
handler: @escaping (
22
-> ImageRequestId
struct ImageRequestResult<T> {
let image: T?
let degraded: Bool
let requestId: ImageRequestId
}
) -> ())ImageRequestResult<T>
ImageSource
protocol ImageSource {
func requestImage<T: InitializableWithCGImage>(
options: ImageRequestOptions,
resultHandler: @escaping (ImageRequestResult<T>) -> ())
-> ImageRequestId
func fullResolutionImageData(completion: @escaping (Data?) -> ())
func imageSize(completion: @escaping (CGSize?) -> ())
func cancelRequest(_: ImageRequestId)
}
23
ImageSource
protocol ImageSource {
func requestImage<T: InitializableWithCGImage>(
options: ImageRequestOptions,
resultHandler: @escaping (ImageRequestResult<T>) -> ())
-> ImageRequestId
func fullResolutionImageData(completion: @escaping (Data?) -> ())
func imageSize(completion: @escaping (CGSize?) -> ())
func cancelRequest(_: ImageRequestId)
}
23
ImageSource
protocol ImageSource {
func requestImage<T: InitializableWithCGImage>(
options: ImageRequestOptions,
resultHandler: @escaping (ImageRequestResult<T>) -> ())
-> ImageRequestId
func fullResolutionImageData(completion: @escaping (Data?) -> ())
func imageSize(completion: @escaping (CGSize?) -> ())
func cancelRequest(_: ImageRequestId)
}
23
Фотогалерея пользователя
24
Photos.framework
PHPhotoLibrary
PHAssetPHAssetPHAssetPHAsset
Фотогалерея пользователя
24
Photos.framework
PHPhotoLibrary
PHAssetPHAssetPHAssetPHAsset
PHImageManager
UIImage
PHImageManager
func requestImage(
for: PHAsset,
targetSize: CGSize,
contentMode: PHImageContentMode,
options: PHImageRequestOptions?,
resultHandler: @escaping (UIImage?, [AnyHashable: Any]?) -> ())
-> PHImageRequestID
25
Наш requestImage для PHAsset
public func requestImage<T : InitializableWithCGImage>(
options: ImageRequestOptions,
resultHandler: @escaping (ImageRequestResult<T>) -> ())
-> ImageRequestId
{
let (phOptions, size, contentMode) = imageRequestParameters(from: options)
var downloadStarted = false
var downloadFinished = false
let startDownload = { (imageRequestId: ImageRequestId) in
downloadStarted = true
if let onDownloadStart = options.onDownloadStart {
dispatch_to_main_queue { onDownloadStart(imageRequestId) }
}
}
let finishDownload = { (imageRequestId: ImageRequestId) in
downloadFinished = true
if let onDownloadFinish = options.onDownloadFinish {
dispatch_to_main_queue { onDownloadFinish(imageRequestId) }
}
}
phOptions.progressHandler = { progress, _, _, info in
let imageRequestId = (info?[PHImageResultRequestIDKey] as? NSNumber)?.int32Value ?? 0
if !downloadStarted {
startDownload(imageRequestId.toImageRequestId())
}
if progress == 1 /* это не reliable, читай ниже */ && !downloadFinished {
finishDownload(imageRequestId.toImageRequestId())
}
}
let id = imageManager.requestImage(for: asset, targetSize: size, contentMode: contentMode, options: phOptions) { [weak self] image, info in
let requestId = (info?[PHImageResultRequestIDKey] as? NSNumber)?.int32Value ?? 0
let degraded = (info?[PHImageResultIsDegradedKey] as? NSNumber)?.boolValue ?? false
let cancelled = (info?[PHImageCancelledKey] as? NSNumber)?.boolValue ?? false || self?.cancelledRequestIds.contains(requestId.toImageRequestId()) == true
let isLikelyToBeTheLastCallback = (image != nil && !degraded) || cancelled
// progressHandler может никогда не вызваться с progress == 1, поэтому тут пытаемся угадать, завершилась ли загрузка
if downloadStarted && !downloadFinished && isLikelyToBeTheLastCallback {
finishDownload(requestId.toImageRequestId())
}
// resultHandler не должен вызываться после отмены запроса
if !cancelled {
resultHandler(ImageRequestResult(
image: (image as? T?).flatMap { $0 } ?? image?.cgImage.flatMap { T(cgImage: $0) },
degraded: degraded,
requestId: requestId.toImageRequestId()
))
}
}
return id.toImageRequestId()
}
26
resultHandler после отмены запроса
27
resultHandler после отмены запроса
PHImageManager
иногда вызывается, иногда — нет
иногда приходит UIImage, иногда — нет
27
resultHandler после отмены запроса
PHImageManager
иногда вызывается, иногда — нет
иногда приходит UIImage, иногда — нет
ImageSource для PHAsset
не вызывается
27
resultHandler после отмены запроса
Отменен ли запрос?
// внутри resultHandler PHImageManager’а
let cancelled =
(info?[PHImageCancelledKey] as? NSNumber)?.boolValue ?? false
|| cancelledRequestIds.contains(requestId)
if !cancelled {
// вызываем "внешний" resultHandler
}
28
resultHandler после отмены запроса
Отменен ли запрос?
// внутри resultHandler PHImageManager’а
let cancelled =
(info?[PHImageCancelledKey] as? NSNumber)?.boolValue ?? false
|| cancelledRequestIds.contains(requestId)
if !cancelled {
// вызываем "внешний" resultHandler
}
28
resultHandler после отмены запроса
Отменен ли запрос?
// внутри resultHandler PHImageManager’а
let cancelled =
(info?[PHImageCancelledKey] as? NSNumber)?.boolValue ?? false
|| cancelledRequestIds.contains(requestId)
if !cancelled {
// вызываем "внешний" resultHandler
}
28
Загрузка из iCloud
29
Загрузка из iCloud
class PHImageRequestOptions { // для PHImageManager

var progressHandler: PHAssetImageProgressHandler?

// ...

}

29
Загрузка из iCloud
class PHImageRequestOptions { // для PHImageManager

var progressHandler: PHAssetImageProgressHandler?

// ...

}

struct ImageRequestOptions { // для ImageSource

var onDownloadStart: ((ImageRequestId) -> ())?

var onDownloadFinish: ((ImageRequestId) -> ())?

// ...

}
29
Загрузка из iCloud
phImageRequestOptions.progressHandler = { progress, _, _, _ in
if progress == 1 {
callOnDownloadFinish()
}
}
30
Загрузка из iCloud
// внутри resultHandler:
let degraded: Bool = info?[PHImageResultIsDegradedKey]
let looksLikeLastCallback =
cancelled || (image != nil && !degraded)
if looksLikeLastCallback {
callOnDownloadFinish()
}
31
Загрузка из iCloud
// внутри resultHandler:
let degraded: Bool = info?[PHImageResultIsDegradedKey]
let looksLikeLastCallback =
cancelled || (image != nil && !degraded)
if looksLikeLastCallback {
callOnDownloadFinish()
}
31
Загрузка из iCloud
// внутри resultHandler:
let degraded: Bool = info?[PHImageResultIsDegradedKey]
let looksLikeLastCallback =
cancelled || (image != nil && !degraded)
if looksLikeLastCallback {
callOnDownloadFinish()
}
31
Загрузка из iCloud
// внутри resultHandler:
let degraded: Bool = info?[PHImageResultIsDegradedKey]
let looksLikeLastCallback =
cancelled || (image != nil && !degraded)
if looksLikeLastCallback {
callOnDownloadFinish()
}
31
ImageIO
ImageIO
Эффективное получение размера
let source = CGImageSourceCreateWithURL(fileUrl, nil)
let options = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil)
let width = options?[kCGImagePropertyPixelWidth] as! Int
let height = options?[kCGImagePropertyPixelHeight] as! Int
let orientation = options?[kCGImagePropertyOrientation] as! Int
if dimensionsSwapped(in: orientation) {
return CGSize(width: height, height: width)
} else {
return CGSize(width: width, height: height)
}
33
ImageIO
Эффективное получение размера
let source = CGImageSourceCreateWithURL(fileUrl, nil)
let options = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil)
let width = options?[kCGImagePropertyPixelWidth] as! Int
let height = options?[kCGImagePropertyPixelHeight] as! Int
let orientation = options?[kCGImagePropertyOrientation] as! Int
if dimensionsSwapped(in: orientation) {
return CGSize(width: height, height: width)
} else {
return CGSize(width: width, height: height)
}
33
ImageIO
Эффективное получение размера
let source = CGImageSourceCreateWithURL(fileUrl, nil)
let options = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil)
let width = options?[kCGImagePropertyPixelWidth] as! Int
let height = options?[kCGImagePropertyPixelHeight] as! Int
let orientation = options?[kCGImagePropertyOrientation] as! Int
if dimensionsSwapped(in: orientation) {
return CGSize(width: height, height: width)
} else {
return CGSize(width: width, height: height)
}
33
ImageIO
Эффективное получение размера
let source = CGImageSourceCreateWithURL(fileUrl, nil)
let options = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil)
let width = options?[kCGImagePropertyPixelWidth] as! Int
let height = options?[kCGImagePropertyPixelHeight] as! Int
let orientation = options?[kCGImagePropertyOrientation] as! Int
if dimensionsSwapped(in: orientation) {
return CGSize(width: height, height: width)
} else {
return CGSize(width: width, height: height)
}
33
ImageIO
Эффективное получение размера
let source = CGImageSourceCreateWithURL(fileUrl, nil)
let options = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil)
let width = options?[kCGImagePropertyPixelWidth] as! Int
let height = options?[kCGImagePropertyPixelHeight] as! Int
let orientation = options?[kCGImagePropertyOrientation] as! Int
if dimensionsSwapped(in: orientation) {
return CGSize(width: height, height: width)
} else {
return CGSize(width: width, height: height)
}
33
Локальные картинки
Эффективный ресайзинг
let source = CGImageSourceCreateWithURL(fileUrl, nil)
let options: [NSString: Any] = [
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height),
kCGImageSourceCreateThumbnailWithTransform: true
]
return CGImageSourceCreateThumbnailAtIndex(source!, 0, options)
34
Локальные картинки
Эффективный ресайзинг
let source = CGImageSourceCreateWithURL(fileUrl, nil)
let options: [NSString: Any] = [
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height),
kCGImageSourceCreateThumbnailWithTransform: true
]
return CGImageSourceCreateThumbnailAtIndex(source!, 0, options)
34
Локальные картинки
Эффективный ресайзинг
let source = CGImageSourceCreateWithURL(fileUrl, nil)
let options: [NSString: Any] = [
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height),
kCGImageSourceCreateThumbnailWithTransform: true
]
return CGImageSourceCreateThumbnailAtIndex(source!, 0, options)
34
Локальные картинки
Эффективный ресайзинг
let source = CGImageSourceCreateWithURL(fileUrl, nil)
let options: [NSString: Any] = [
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height),
kCGImageSourceCreateThumbnailWithTransform: true
]
return CGImageSourceCreateThumbnailAtIndex(source!, 0, options)
34
Локальные картинки
Эффективный ресайзинг
let source = CGImageSourceCreateWithURL(fileUrl, nil)
let options: [NSString: Any] = [
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height),
kCGImageSourceCreateThumbnailWithTransform: true
]
return CGImageSourceCreateThumbnailAtIndex(source!, 0, options)
34
Images & memory
35
Images & memory
для операций над изображениями — Core Image, ImageIO
35
Images & memory
для операций над изображениями — Core Image, ImageIO
создавайте UIImage минимально необходимого размера
35
Images & memory
для операций над изображениями — Core Image, ImageIO
создавайте UIImage минимально необходимого размера
не храните больше UIImage, чем помещается на экране
35
github.com/avito-tech/Paparazzo
github.com/avito-tech/Paparazzo
ayutkin@avito.ru

Андрей Юткин. Media Picker — to infinity and beyond