Media PickerTo 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
Настройка AVCaptureSessionlet videoDevices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo)
let backCamera = videoDevices?.first { $0.position == .back }
let input = try AVCaptureDeviceInput(device: backCamera)
6
Настройка AVCaptureSessionlet 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
Настройка AVCaptureSessionlet captureSession = AVCaptureSession() captureSession.sessionPreset = AVCaptureSessionPresetPhoto
if captureSession.canAddInput(input) { captureSession.addInput(input) }
if captureSession.canAddOutput(output) { captureSession.addOutput(output) }
captureSession.startRunning()
7
Настройка AVCaptureSessionlet captureSession = AVCaptureSession() captureSession.sessionPreset = AVCaptureSessionPresetPhoto
if captureSession.canAddInput(input) { captureSession.addInput(input) }
if captureSession.canAddOutput(output) { captureSession.addOutput(output) }
captureSession.startRunning()
7
Настройка AVCaptureSessionlet captureSession = AVCaptureSession() captureSession.sessionPreset = AVCaptureSessionPresetPhoto
if captureSession.canAddInput(input) { captureSession.addInput(input) }
if captureSession.canAddOutput(output) { captureSession.addOutput(output) }
captureSession.startRunning()
7
Настройка AVCaptureSessionlet captureSession = AVCaptureSession() captureSession.sessionPreset = AVCaptureSessionPresetPhoto
if captureSession.canAddInput(input) { captureSession.addInput(input) }
if captureSession.canAddOutput(output) { captureSession.addOutput(output) }
captureSession.startRunning()
7
Настройка AVCaptureSessionlet 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
UIViewCMSampleBuffer
iPhone 5s и выше
Рендеринг CMSampleBuffer
11
UIViewCMSampleBuffer
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
Отображение в UIlet viewSize: CGSizelet contentMode: ContentMode // enum: aspectFit/aspectFill
19
Отображение в UIlet viewSize: CGSizelet contentMode: ContentMode // enum: aspectFit/aspectFill
let handler = { (image: UIImage?) in imageView.image = image}
19
Отображение в UIlet viewSize: CGSizelet contentMode: ContentMode // enum: aspectFit/aspectFill
let handler = { (image: UIImage?) in imageView.image = image}
let deliveryMode: DeliveryMode // enum: progressive/best
19
Отображение в UIfunc requestImage(
20
viewSize: CGSize, contentMode: ContentMode, deliveryMode: DeliveryMode, handler: @escaping (UIImage?) -> ())
Отображение в UIfunc requestImage(
20
handler: @escaping (UIImage?) -> ()) options: ImageRequestOptions,
struct ImageRequestOptions { let viewSize: CGSize let contentMode: ContentMode let deliveryMode: DeliveryMode }
Отображение в UIprotocol InitializableWithCGImage { init(cgImage: CGImage) }
21
Отображение в UIprotocol InitializableWithCGImage { init(cgImage: CGImage) }
extension UIImage: InitializableWithCGImage {} extension NSImage: InitializableWithCGImage {}
21
Отображение в UIfunc requestImage<T: InitializableWithCGImage>( options: ImageRequestOptions, handler: @escaping (
22
) -> ())T?
Отображение в UIfunc requestImage<T: InitializableWithCGImage>( options: ImageRequestOptions, handler: @escaping (
22
-> ImageRequestId) -> ())T?
Отображение в UIfunc requestImage<T: InitializableWithCGImage>( options: ImageRequestOptions, handler: @escaping (
22
-> ImageRequestId
struct ImageRequestResult<T> { let image: T? let degraded: Bool let requestId: ImageRequestId }
) -> ())ImageRequestResult<T>
ImageSourceprotocol 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
ImageSourceprotocol 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
ImageSourceprotocol 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
PHAssetPHAssetPHAssetPHAssetPHImageManager
UIImage
PHImageManagerfunc requestImage( for: PHAsset, targetSize: CGSize, contentMode: PHImageContentMode, options: PHImageRequestOptions?, resultHandler: @escaping (UIImage?, [AnyHashable: Any]?) -> ()) -> PHImageRequestID
25
Наш requestImage для PHAssetpublic 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
Загрузка из iCloudclass PHImageRequestOptions { // для PHImageManager var progressHandler: PHAssetImageProgressHandler? // ...}
29
Загрузка из iCloudclass PHImageRequestOptions { // для PHImageManager var progressHandler: PHAssetImageProgressHandler? // ...}
struct ImageRequestOptions { // для ImageSource var onDownloadStart: ((ImageRequestId) -> ())? var onDownloadFinish: ((ImageRequestId) -> ())? // ...}
29
Загрузка из iCloudphImageRequestOptions.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/[email protected]