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

Preview:

Citation preview

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/Paparazzoayutkin@avito.ru