TOCropViewController – An open source image cropper for iOS

Over the years, I’ve built up a pretty crazy list of features that I want to add to iComics. One feature on that list that’s been kicking around since pretty much day 1 has been the ability to crop page images.

Photo cropping as a native feature is not new to iOS. It was originally introduced in iOS 5 wayyy back in 2011, and was touted during the unveiling of the iPhone 4S.

iPhone-4S-Cropper

Since then, it’s only gotten WAY cooler in iOS 8.

iPhone-6-Cropper

I’ve wanted the ability to crop page images in iComics for mainly two reasons: to make comic thumbnails more configurable, and to make posting small portions of comic pages to social media possible. I’m also considering persistent image editing down the line as well, but that still requires a bit of research.

Sadly, Apple has never made the cropping feature of the Photos app accessible to third party apps. As a result of this, a LOT of third party cropping libraries have popped up over the years to get around the solution. Most notably, Katsumi Kishikawa, one of my colleagues from Realm released quite possibly the best iOS 5-styled cropper library on GitHub.

PEPhotoCropper

Initially, I was looking forward to using this one in iComics. But sadly, when iOS 7 hit, I was forced to put that feature on the back burner while I redesigned my app. When I finally had the app ready, iOS 8 had come around, and the cropper’s design both started looking dated, and was behaving strangely on my iPhone 6 Plus. Katsumi’s told me that he does want to upgrade the cropper at some point, but sadly simply hasn’t had the time lately.

As a result of all of that, I decided that it might be worth to write a new cropper from scratch after all; something designed from the start to match what users were already expecting on iOS 8. After a quick proof-of-concept, I started writing some code in March this year, and posted the finished library to GitHub two weeks ago:

TOCropViewController-WWDC

Named TOCropViewController, the goal of this library was to provide the ability to crop a UIImage object and either return it to an object, or immediately share it. It was designed with the iOS 8 Photos app in mind, taking advantage of the system translucency and springy iOS 7 animations. As a few extra niceties, it also allows rotating images in 90-degree chunks and aspect-locking the crop box.

In terms of how everything actually works, here’s a 3D breakout of what layers comprise the view:

 

TOCropViewController BreakdownFor most normal cropping implementations, it makes sense to simply have 4 views overlaying the image, creating the square ‘hole’ in the middle denoting which region will be cropped. Since having to do that with both the translucent layer, and the dimming layer was looking to be quite complex, I opted to try a new approach: place a copy of the image above the layers, clipped to a view that was denoting the cropping region, and then automatically line up the foreground image with the background image. This ended up working flawlessly, creating the illusion that there is a ‘hole’ in the middle of the view, and minimising the amount of views on screen needed to create that effect.

In any case, this was definitely one of the harder views I’ve worked on. There were a LOT of alignment issues involving the crop box and the floating point precision of the scroll view, so I spent a fair bit of time simply making sure float values were being clipped to integer values properly throughout the class.

Now that the view is finished, I’m very happy with the results and can’t wait to get it up and running in iComics as soon as I can. If you’re also writing iOS software and would like to use this library, it’s available on GitHub under the MIT license.

Enjoy!

 

  • Florian Coulon

    Your library is perfect ! It would be very nice if you add the rotate wheel and it could be the best crop library ever made.
    I create an issue for this on Github.

    • Thanks a lot Florian! I’m glad you like it!

      Ahh… I REALLY want to add fine-grain rotations, but it’s the sort of the thing that would require a huge amount of research to get working properly, and it’s not something I can spend the time on right now.

      Thanks for the pull request! I’ll follow this thread up there, and leave it open in case anyone else wants to take up the challenge. ;)

      • Florian Coulon

        I’m not a huge iOS developer, but i will try to add it.

  • TheSimplest.Net

    This is brilliant work. The 90 degrees rotation while crop border adjusts itself accordingly is exactly what I need and no other crop library that I came across can do that perfectly :)

    • Thanks a lot! I’m glad you like it!

      Haha yeah, the 90 degree rotation feature turned out to be slightly harder than I thought. I discovered that due to the floating point imprecision, if you rotated it many times, the crop box would gradually shrink. I had to fix that by explicitly keeping a copy of the original crop box sizes before rotation and lining the size back up to it.

      I’m glad to hear that that was all worth it. :)

  • focorner

    I just tried it and I’m impressed. You must have put a lot of work into this. Great job. Thank you very much for making this available to everyone. A+ :)

    • Thanks a lot man! Glad you like it! :)
      Haha yeah! It was definitely a month or so in development. But since there doesn’t appear to be any other iOS 7 style croppers out there, I wanted to make sure this one did the job properly! :)
      My pleasure! Enjoy! :)

  • Saurabh

    I just loved your code . I have one problem, i want the cropper size to be fixed . How to implement that ?

    Thanks .

    • Thanks a lot! Um, it doesn’t support hard-coded size limits at the moment. Feel free to add that as a feature! :)

  • Avinash Dadhich

    I am calling the TOCropViewController from another UIViewController but when I am trying to save the delegate as,
    cropViewController.delegate = self,
    It gives me a incompatible types warning why ?

    • Um, is your view controller declaring it implements the TOCropViewController delegate protocol?

  • PRASHANT SAWANT

    I have tried it and its amazing tut. Just i want to add zoom out functionality, once we slide cropping window little bit outside main window and then zoom out the image. Instead reset on click of button just zoom out image as we move cropping window little bit outside main window. Can I achieve it ?

    • Um, I’m not exactly sure what sort of effect you’re trying to achieve there. Sorry!

      • Solublepeter

        I think he is wanting to zoom out, past the bounds of an image, filling the newly-revealed area with black (or a color, typically taken from the top left pixel of the image)

        That’s my guess, anyway.

  • Joaquin

    Hi tim!!! I has import this Objective-C librari into a Swift project, with a bridge header, but when i try to do a presentViewController it trow me this error:

    ERROR:

    2016-03-20 21:59:30.214 ImageTest6[20286:2042246] *** Terminating app due to uncaught exception ‘CALayerInvalidGeometry’, reason: ‘CALayer position contains NaN: [nan nan]’

    *** First throw call stack:

    (

    0 CoreFoundation 0x000000010cc97e65 __exceptionPreprocess + 165

    1 libobjc.A.dylib 0x000000010c710deb objc_exception_throw + 48

    2 CoreFoundation 0x000000010cc97d9d +[NSException raise:format:] + 205

    3 QuartzCore 0x0000000111e8ca86 _ZN2CA5Layer12set_positionERKNS_4Vec2IdEEb + 152

    4 QuartzCore 0x0000000111e8cbf9 -[CALayer setPosition:] + 44

    5 QuartzCore 0x0000000111e8d25d -[CALayer setFrame:] + 650

    6 UIKit 0x000000010d30b25f -[UIView(Geometry) setFrame:] + 356

    7 ImageTest6 0x000000010c1d1b3e -[TOCropView setCropBoxFrame:] + 1518

    8 ImageTest6 0x000000010c1cd0a8 -[TOCropView layoutInitialImage] + 1432

    9 ImageTest6 0x000000010c1ccb09 -[TOCropView didMoveToSuperview] + 73

    10 UIKit 0x000000010d31221d __45-[UIView(Hierarchy) _postMovedFromSuperview:]_block_invoke + 590

    11 UIKit 0x000000010d311f69 -[UIView(Hierarchy) _postMovedFromSuperview:] + 544

    12 UIKit 0x000000010d31fd8f -[UIView(Internal) _addSubview:positioned:relativeTo:] + 1967

    13 ImageTest6 0x000000010c1dd50e -[TOCropViewController viewDidLoad] + 830

    14 UIKit 0x000000010d402f98 -[UIViewController loadViewIfRequired] + 1198

    15 UIKit 0x000000010d4032e7 -[UIViewController view] + 27

    16 UIKit 0x000000010dbadf87 -[_UIFullscreenPresentationController _setPresentedViewController:] + 87

    17 UIKit 0x000000010d3d2f62 -[UIPresentationController initWithPresentedViewController:presentingViewController:] + 133

    18 UIKit 0x000000010d415c8c -[UIViewController _presentViewController:withAnimationController:completion:] + 4002

    19 UIKit 0x000000010d418f2c -[UIViewController _performCoordinatedPresentOrDismiss:animated:] + 489

    20 UIKit 0x000000010d418a3b -[UIViewController presentViewController:animated:completion:] + 179

    21 ImageTest6 0x000000010c1e703a _TFC10ImageTest614ViewController11viewDidLoadfS0_FT_T_ + 794

    22 ImageTest6 0x000000010c1e71c2 _TToFC10ImageTest614ViewController11viewDidLoadfS0_FT_T_ + 34

    23 UIKit 0x000000010d402f98 -[UIViewController loadViewIfRequired] + 1198

    24 UIKit 0x000000010d4032e7 -[UIViewController view] + 27

    25 UIKit 0x000000010d2d9ab0 -[UIWindow addRootViewControllerViewIfPossible] + 61

    26 UIKit 0x000000010d2da199 -[UIWindow _setHidden:forced:] + 282

    27 UIKit 0x000000010d2ebc2e -[UIWindow makeKeyAndVisible] + 42

    28 UIKit 0x000000010d264663 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4131

    29 UIKit 0x000000010d26acc6 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1760

    30 UIKit 0x000000010d267e7b -[UIApplication workspaceDidEndTransaction:] + 188

    31 FrontBoardServices 0x0000000110f09754 -[FBSSerialQueue _performNext] + 192

    32 FrontBoardServices 0x0000000110f09ac2 -[FBSSerialQueue _performNextFromRunLoopSource] + 45

    33 CoreFoundation 0x000000010cbc3a31 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17

    34 CoreFoundation 0x000000010cbb995c __CFRunLoopDoSources0 + 556

    35 CoreFoundation 0x000000010cbb8e13 __CFRunLoopRun + 867

    36 CoreFoundation 0x000000010cbb8828 CFRunLoopRunSpecific + 488

    37 UIKit 0x000000010d2677cd -[UIApplication _run] + 402

    38 UIKit 0x000000010d26c610 UIApplicationMain + 171

    39 ImageTest6 0x000000010c1e81fd main + 109

    40 libdyld.dylib 0x000000010fba792d start + 1

    )

    libc++abi.dylib: terminating with uncaught exception of type NSException

    THE CODE I USE:

    class ViewController: UIViewController,TOCropViewControllerDelegate {

    override func viewDidLoad() {

    super.viewDidLoad()

    var image:UIImage = UIImage()

    var cropViewController = TOCropViewController(image: image)

    cropViewController.delegate = self

    self.presentViewController(cropViewController, animated: true, completion: nil)

    }

    func cropViewController(cropViewController: TOCropViewController!, didCropToImage image: UIImage!, withRect cropRect: CGRect, angle: Int) {

    print(“FINAL”)

    }

    }

    Do you have any idea how i can do it to get it working??

    It is a wonderfoul library but i am having problems importing in swift…

    THANK YOU VERY MUCH !!!!

    • Thanks Joaquin!

      Looks like your Swift bridge is working perfectly. The only problem is you can’t feed a blank UIImage into it; it must be a proper UIImage at the time of creation. That CALayer NaN occurs when it tries to perform some layout math and the size of the UIImage is 0x0. I probably should add some better checks to that… XD

      Anyway, good luck!

      • Solublepeter

        Did you manage to create a Swift version, Joaquin. Is that open-source?

  • Joaquin

    (Or maybe a little tuto to getting usefoul in swift.. :)

  • Joaquin

    Ey Tim!!!!! I was able to get it working.

    I understand how to use your library after read your answer and after a couple of hours.

    I like your Library so much!! Is the best free library to basic edit image. Thank you very much for the aport man!

    :)

  • Andrew Dzhur

    Hey Tim, i have a problem: Unable to find a specification for `TOCropViewController`. Please help me – [email protected]

    • Uh, sorry Andrew! Uh, I have no idea what that means. ^_^;;

      I’ve tried to make this as clear as possible, but I don’t have the time to provide specialised support for my personal libraries. Sorry!

  • Krishna Dubagunta

    Hey tim,
    got this linker error while building

    Proj.ViewController.presentCropController () -> () in ViewController.o

    function signature specialization of Proj.ViewController.viewWillAppear (Swift.Bool) -> () in ViewController.o

    Any idea how to remove this?

    • Hi Krishna! I replied to your issue for this matter on GitHub. :)

  • Er Nitesh Sharma

    hey,
    how to change only image of ToCropView on the didSelect function of UICollectionView??
    please reply as soon as possible because it’s very urgent for me right now.

  • Baltej Singh

    Hi Tim i was using your library but i face some weird issue. it’s get small or big as i am resizing crop view its image getting reszie too not judt crop so how to resolve it please help me

    • Sorry! I don’t do support requests on my blog. Please file an issue on the GitHub repo.

  • Kumait Mohammed

    Hello Tim,
    Amazing work. I have successfully created a binding project to your library using Xamarin and it works great.
    Thanks a lot,

    • Hey Kumait! Awesome! I’m glad you found my library useful! :) Thanks so much!

  • Falcon

    Hey Tim,

    Great Work Man!! Thanks for sharing the library..

    • Hey Falcon! Thanks so much! I hope you like it! :)

  • Great work!! I use this library for many projects :D

    • Yay! I’m really happy to hear that! Thank you! :D

  • Fazoomd

    Working great!! But how can i set square as a default crop option ? Thanks in advance.

  • Christoffer Årstrand

    Just wanted to say thanks for sharing this class initially and to all the people contributing over the years. =) Wish I found this one earlier…