Skip to content

Instantly share code, notes, and snippets.

@ManueGE
Last active November 6, 2018 20:44
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ManueGE/f1f3f34a8b3c7fab8d0b974a60011f60 to your computer and use it in GitHub Desktop.
Save ManueGE/f1f3f34a8b3c7fab8d0b974a60011f60 to your computer and use it in GitHub Desktop.
ImagePicker+TOCropViewController

Description:

Puts ImagePicker and TOCropViewController together.

Requirements:

In your Podfile:

  pod 'ImagePicker', '~> 2.0'
  pod 'TOCropViewController', '~> 2.0'

Usage:

let imagePicker = ImagePickerController()
imagePicker.imageLimit = 1

let pickerCropper = ImagePickerCropper(picker: defaultImagePicker, cropperConfigurator: { image in
  let cropController = TOCropViewController(image: image)
  cropController.aspectRatioLockEnabled = true
  cropController.resetAspectRatioEnabled = false
  cropController.rotateButtonsHidden = true
  cropController.customAspectRatio = CGSize(width: 2, height: 1)
  cropController.modalTransitionStyle = .crossDissolve
            
  return cropController
})

pickerCropper.show(from: self) { result in
  if case let .success(images) = result, let image = images.first {
    self.image = image
  }
}
//
// ImagePicker.swift
// manueGE
//
// Created by Manuel García-Estañ on 7/11/16.
// Copyright © 2016 ManueGE. All rights reserved.
//
import Foundation
import UIKit
import ImagePicker
import TOCropViewController
/// A image picker that can crop the image after selecting it
struct ImagePickerCropper {
typealias Completion = (Result) -> Void
typealias CropperCreator = (UIImage) -> TOCropViewController
enum Error: Swift.Error {
case cancelPick
case cancelCrop
}
enum Result {
case success([UIImage])
case cancelled(Error)
}
/// The picker used to select the image(s)
let imagePicker: ImagePickerController
/// The closure used to create the `TOCropViewController`
let cropperConfigurator: CropperCreator?
/// Creates a new instance with the given picker and cropper configuration
init(picker: ImagePickerController, cropperConfigurator: CropperCreator? = nil) {
self.imagePicker = picker
self.cropperConfigurator = cropperConfigurator
}
/// Show the picker from the given controller.
func show(from controller: UIViewController, animated: Bool = true, completion: @escaping Completion) {
imagePicker.delegate = PickerCropperDelegate(baseController: controller,
pickerCropper: self,
completion: completion)
controller.present(imagePicker, animated: animated, completion: nil)
}
}
/// The delegate which will handle the responses from picker and cropper
private class PickerCropperDelegate: NSObject {
let baseController: UIViewController
let pickerCropper: ImagePickerCropper
let completion: ImagePickerCropper.Completion
fileprivate var pickedImages: [UIImage] = []
fileprivate var croppedImages: [UIImage] = []
fileprivate var retainCycle: AnyObject?
init(baseController: UIViewController, pickerCropper: ImagePickerCropper, completion: @escaping ImagePickerCropper.Completion) {
self.baseController = baseController
self.pickerCropper = pickerCropper
self.completion = completion
super.init()
self.retainCycle = self
}
fileprivate func dismiss(with result: ImagePickerCropper.Result) {
completion(result)
baseController.dismiss(animated: true, completion: nil)
retainCycle = nil
}
}
// MARK: Picker
extension PickerCropperDelegate: ImagePickerDelegate {
// MARK: Protocol
fileprivate func doneButtonDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) {
finishPicker(from: imagePicker, with: images)
}
fileprivate func cancelButtonDidPress(_ imagePicker: ImagePickerController) {
finishPicker(from: imagePicker, with: [])
}
fileprivate func wrapperDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) {
finishPicker(from: imagePicker, with: images)
}
// Finish
private func finishPicker(from controller: ImagePickerController, with images: [UIImage]) {
// If empty, we send cancel
guard let firstImage = images.first else {
dismiss(with: .cancelled(.cancelPick))
return
}
if let _ = pickerCropper.cropperConfigurator {
pickedImages = images
showCropper(with: firstImage)
}
else {
dismiss(with: ImagePickerCropper.Result.success(images))
}
}
}
// MARK: Cropper
extension PickerCropperDelegate: TOCropViewControllerDelegate {
fileprivate func sendResult() {
dismiss(with: .success(croppedImages))
}
func showCropper(with image: UIImage) {
guard let configurator = pickerCropper.cropperConfigurator else {
fatalError("Shouldn't be here without a cropper configurator")
}
let cropper = configurator(image)
cropper.delegate = self
let imagePicker = pickerCropper.imagePicker
if let _ = imagePicker.presentedViewController {
imagePicker.dismiss(animated: true) {
imagePicker.present(cropper, animated: true, completion: nil)
}
}
else {
imagePicker.present(cropper, animated: true, completion: nil)
}
}
fileprivate func cropViewController(_ cropViewController: TOCropViewController, didCropTo image: UIImage, with cropRect: CGRect, angle: Int) {
croppedImages.append(image)
if croppedImages.count == pickedImages.count {
sendResult()
}
else {
let nextIndex = croppedImages.count
let nextImage = pickedImages[nextIndex]
showCropper(with: nextImage)
}
}
fileprivate func cropViewController(_ cropViewController: TOCropViewController, didFinishCancelled cancelled: Bool) {
dismiss(with: .cancelled(.cancelCrop))
}
}
@agordeev
Copy link

Note that

fileprivate func cropViewController(_ cropViewController: TOCropViewController, didCropTo image: UIImage, with cropRect: CGRect, angle: Int)

is changed to

fileprivate func cropViewController(_ cropViewController: TOCropViewController, didCropToImage image: UIImage, rect cropRect: CGRect, angle: Int)

now.

@agordeev
Copy link

Also baseController.dismiss(animated: true, completion: nil) dismisses the baseController too if it's modal and the user pressed Cancel without cropping. I had to change that line to:

    if pickerCropper.imagePicker.presentedViewController != nil {
        baseController.dismiss(animated: true, completion: nil)
    } else {
        baseController.presentedViewController?.dismiss(animated: true, completion: nil)
    }

@jlichti
Copy link

jlichti commented Oct 13, 2017

Great gist - highly appreciated!

Also thanks to @agordeev as I used both changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment