Background
Hello. I needed some place (before I start the topic) to say why I started to making an app for image cropping. Hence I am writing this annoying paragraph.
One year back I started to making apps for Android as that time I had windows PC and android phone and off course time to experiment. I saw one wallpaper in some wallpaper thread in popular android forum which I liked very much. It was simply a blurred image with a circle revealing the actual image. I wanted to make an app which can do exactly the same thing with few taps. So I published my first ever app in android (see fig). If you have time you can look into this app.
I started making more apps for android.
Since I have little knowledge in programming I wanted to learn the sweet swift language. But I know I can’t make apps with writing hello world app. So I started to make an app which required a Image Cropping as an option with pre-defined ratio. So I thought I should share – how I made an image cropping app with Swift 2.
Image Crop
I wont call this a tutorial, instead its a “code with me” type write up 🙂
In order to make image cropping app I needed these
- picking image from device(iPhone/iPad)
- Display the full image
- Drag-zoom enabled rectangle to select a area.
- get the same area from the image as well.
- Showing some pre-defined output image ratio.
- Showing the result
- Saving/ re-selecting image
** I know there must be better options making such apps. But I wanted to make it in my way.
With my effort so far I have made this (see images)
First Thing First – Making an single view application with Xcode:
First make a single view application with default options. Name as we wish. For simplicity we will add toolbar and bar button item. We will add three only 1) for picking image from iDevice 2) Confirming crop and 3) for giving few options (see crop options in image above).
Hence we get our VC similar to this image.
I also embedded the VC into Navigation Controller. Also I made another view controller to show the cropped image and with “Crop Again” and “Save” Button.
Setting up the VCS:
Xcode creates first view controller’s class View Controller. Swift which got two functions viewDidLoad() and didReceiveMemoryWarning() . I have not added Image View in the storyboard. I will create it on viewDidLoad() function.
Showing Image In UIImageView:
var imgView: UIImageView! override func viewDidLoad() { super.viewDidLoad() imgView = UIImageView(frame: self.view.frame) imgView.contentMode = .ScaleAspectFit imgView.clipsToBounds = true self.view.addSubview(imgView) }
Now choosing image from iDevices. Need to use UIImagePickerController and hence we need to use UIImagePickerControllerDelegate and UINavigationControllerDelegate. Then we make a variable picker
let picker = UIImagePickerController() and in the viewDidLoad function we pass delegate as picker.delegate = self
Now we need to bind a function with the “Pick Image” button (control drag to VC in storyboard) like this –
Well I also made one variable to store chosen image. I put lot of print() to get the coordinates (my first swift programming :D)
@IBAction func AddImage(sender: AnyObject) { picker.allowsEditing = false picker.sourceType = .PhotoLibrary presentViewController(picker, animated: true, completion: nil) } func imagePickerController( picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) { chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage imgView.image = chosenImage calculateRect() if !added { self.makeCropAreaVisible() added = true } print("now crop area on top \(cropview.frame) ") dismissViewControllerAnimated(true, completion: nil) print("image found with width \(chosenImage.size.width) and height \(chosenImage.size.height)") print("image view frame width \(imgView.frame.width) and height \(imgView.frame.height)") } func imagePickerControllerDidCancel(picker:UIImagePickerController { dismissViewControllerAnimated(true, completion: nil) }
calculateRect() and makeCropAreaVisible() actually taking care of the are below the cropping rectangle. We will see them later.
Creating UIView for Crop Area:
I simply added a UIView on the root view and made the view drag-able and zoomable. So I had to write a custom UIView like this –
init(origin: CGPoint, width: CGFloat, height: CGFloat) { super.init(frame: CGRectMake(0.0, 0.0, width, height)) _cropoptions = CROP_OPTIONS() _cropoptions.Center = origin let insetRect = CGRectInset(self.bounds,lineWidth,lineWidth) self.path = UIBezierPath(roundedRect: insetRect, cornerRadius: 0.0) self.center = origin self.fillColor = UIColor(red: 0.09, green: 0.56, blue: 0.8, alpha: 0.2) _cropoptions.Width = self.bounds.width _cropoptions.Height = self.bounds.height self.backgroundColor = UIColor.clearColor() addGestureRecognizer(UIPanGestureRecognizer(target: self, action: "panning:")) addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: "pinching:")) }
func panning(panGR: UIPanGestureRecognizer) { self.superview!.bringSubviewToFront(self) var translation = panGR.translationInView(self) translation = CGPointApplyAffineTransform(translation, self.transform) self.center.x += translation.x self.center.y += translation.y panGR.setTranslation(CGPointZero, inView: self) _cropoptions.Center = self.center }
func pinching(pinchGR: UIPinchGestureRecognizer) { self.superview!.bringSubviewToFront(self) let scale = pinchGR.scale self.transform = CGAffineTransformScale(self.transform, scale, scale) _cropoptions.Height = self.frame.height _cropoptions.Width = self.frame.width pinchGR.scale = 1.0 }
Also made the overridden drawrect like this –
override func drawRect(rect: CGRect) { self.fillColor.setFill() self.path.fill() self.path.lineWidth = self.lineWidth UIColor.whiteColor().setStroke() self.path.stroke() }
When added to view this will make a zoomable and drag-able rectangle like this.
Now I wanted to calculate the same area in the image – hence I needed the co-ordinate in the view as well as in the image.
Getting Data from Crop Area:
Well to get the rectangle’s coordinate I needed some global variable – for simplicity I added a swift file like this –
struct xy { var x: CGFloat! var y: CGFloat! mutating func xy(_x: CGFloat , _y: CGFloat){ self.x = _x self.y = _y } } enum CROP_TYPE{ case square, rect3x2, rect2x3, rect3x4, rect4x3, rect2x5, rect5x2 static let divs = [square : 1, rect3x2 : 5, rect2x3 : 5,rect3x4 : 7, rect4x3 : 7, rect2x5 : 7,rect5x2 : 7] static let muls = [square : xy(x: 0.5, y: 0.5), rect3x2 : xy(x: 3/5, y: 2/5), rect2x3 : xy(x: 2/5, y: 2/5),rect3x4 : xy(x: 3/7, y: 4/7), rect4x3 : xy(x: 4/7, y: 3/7), rect2x5 : xy(x: 2/7, y: 5/7),rect5x2 : xy(x: 5/7, y: 2/7)] static let names = [square : " 1:1 ", rect3x2 : " 3:2 ", rect2x3 : " 2:3 ",rect3x4 : " 3:4 ", rect4x3 : " 4:3 ", rect2x5 : " 2:5 ",rect5x2 : " 5:2 "] func Div()-> Int{ if let ret = CROP_TYPE.divs[self]{ return ret }else{ return -1 } } func Muls()-> xy{ if let ret = CROP_TYPE.muls[self]{ return ret }else{ return xy(x: 0.5,y: 0.5) } } func Name()-> String{ if let ret = CROP_TYPE.names[self]{ return ret }else{ return "n.a." } } } struct CROP_OPTIONS { var Height: CGFloat! var Width: CGFloat! var Center: CGPoint! } struct FRAME { var Height: CGFloat! var Width: CGFloat! } var _cropoptions: CROP_OPTIONS! var _frame: CROP_OPTIONS! var croptype: CROP_TYPE!
Almost self explanatory. The enum CROP_TYPE is the crop options – i added two functions for names and multiplication factor of the rect. This will be used in creating cropping rectangle and use is like this –
func makeCropAreaVisible(){ //making selected crop area cropview.removeFromSuperview() cropview = nil let min: CGFloat = imgviewrect.size.height > imgviewrect.size.width ? imgviewrect.size.width:imgviewrect.size.height cropview = CustomCropView(origin: self.view.center, width: min*croptype.Muls().x, height: min*croptype.Muls().y) self.view.addSubview(cropview) } func calculateRect(){ // getting same value from the image imgviewrect = AVMakeRectWithAspectRatioInsideRect(chosenImage.size, imgView.bounds) print (" Image Frame height:\(imgviewrect.size.height) width:\(imgviewrect.size.width) and x,y =( \(imgviewrect.origin.x) ,\(imgviewrect.origin.y) )" ) }
Giving user option to select W:H ratio:
To make Alert dialog with various option mentioned we need this function (bind with the option item button )
@IBAction func CropOptions(sender: AnyObject) { print("Choose crop options \(croptype.Name()))") let myActionSheet = UIAlertController(title: "Crop Options[\(croptype.Name())]", message: "Select Crop Ratio (width: height) ", preferredStyle: UIAlertControllerStyle.ActionSheet) let op1 = UIAlertAction(title: CROP_TYPE.square.Name(), style: UIAlertActionStyle.Default) { (action) in croptype = CROP_TYPE.square self.makeCropAreaVisible() } let op2 = UIAlertAction(title: CROP_TYPE.rect3x2.Name(), style: UIAlertActionStyle.Default) { (action) in croptype = CROP_TYPE.rect3x2 self.makeCropAreaVisible() } let op3 = UIAlertAction(title: CROP_TYPE.rect2x3.Name(), style: UIAlertActionStyle.Default) { (action) in croptype = CROP_TYPE.rect2x3 self.makeCropAreaVisible() } let op4 = UIAlertAction(title: CROP_TYPE.rect3x4.Name(), style: UIAlertActionStyle.Default) { (action) in croptype = CROP_TYPE.rect3x4 self.makeCropAreaVisible() } let op5 = UIAlertAction(title: CROP_TYPE.rect4x3.Name(), style: UIAlertActionStyle.Default) { (action) in croptype = CROP_TYPE.rect4x3 self.makeCropAreaVisible() } let op6 = UIAlertAction(title: CROP_TYPE.rect2x5.Name(), style: UIAlertActionStyle.Default) { (action) in croptype = CROP_TYPE.rect2x5 self.makeCropAreaVisible() } let op7 = UIAlertAction(title: CROP_TYPE.rect5x2.Name(), style: UIAlertActionStyle.Default) { (action) in croptype = CROP_TYPE.rect5x2 self.makeCropAreaVisible() } let op10 = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel) { (action) in print("Cancel action button tapped") } myActionSheet.addAction(op1) myActionSheet.addAction(op2) myActionSheet.addAction(op3) myActionSheet.addAction(op4) myActionSheet.addAction(op5) myActionSheet.addAction(op6) myActionSheet.addAction(op7) myActionSheet.addAction(op10) self.presentViewController(myActionSheet, animated: true, completion: nil) }
Pushing cropped image to New VC:
Almost all done. Now we need to get the cropped image and push it to new view controller. With the “Crop” button I used segue and named it as “showCroppedImg” . But we need to perform or task “getting cropped image” before showing the second VC. Here is the solution
override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) { if segue!.identifier == "showCroppedImg" { retriveCroppedImage() } }
And getting cropped image is as simple as this –
func retriveCroppedImage(){ let yratio: CGFloat = imgviewrect.size.height / chosenImage.size.height let xratio: CGFloat = imgviewrect.size.width / chosenImage.size.width var cliprect = CGRectMake(_cropoptions.Center.x - _cropoptions.Width/2, _cropoptions.Center.y - _cropoptions.Height/2, _cropoptions.Width, _cropoptions.Height) print("cliprect top \(cliprect.size)") cliprect.size.height = cliprect.size.height / xratio; cliprect.size.width = cliprect.size.width / xratio; cliprect.origin.x = cliprect.origin.x / xratio + imgviewrect.origin.x / xratio cliprect.origin.y = cliprect.origin.y / yratio - imgviewrect.origin.y / xratio print("cliprect On Image \(cliprect)") let imageRef = CGImageCreateWithImageInRect(chosenImage.CGImage, cliprect ) croppedImg = UIImage(CGImage: imageRef!, scale: UIScreen.mainScreen().scale, orientation: chosenImage.imageOrientation) print("Operation complete"); }
Saving Image:
Now saving the image is in the second view controller.
@IBAction func SaveToAlbum(sender: AnyObject) { UIImageWriteToSavedPhotosAlbum(croppedImg, self, "image:didFinishSavingWithError:contextInfo:", nil) } func image(image: UIImage, didFinishSavingWithError error: NSErrorPointer, contextInfo:UnsafePointer<Void>) { if error != nil { let alert=UIAlertController(title: "Opps!", message: "Your Cropped Image could not be saved", preferredStyle: UIAlertControllerStyle.Alert); showViewController(alert, sender: self); } }
All done. I am now making the app where I wanted to use crop functionality. I needed almost two week (read weekend) to make this functionality.
Here is more screen shots with 5:2 and 3:4 ratio cropping 🙂
Here is another approach to crop image I recently wrote . Have a look in this post – which is more easier
Hi Can u add your project files?
LikeLike
[…] iOS Swift Image Crop Swift- Learning Stack Layout Get fonts list in iOS Device Swift Text Customisation Page View […]
LikeLike
Waht are you doing in CustomCropView() method ?
LikeLike
CustomCropView() method calls the custom UIView. This custom UIView has pan and zoom handler registered. Actually it makes a view – which defines the ares for crop. The code under the heading “Creating UIView for Crop Area: ” is the class for CustomCropView.
LikeLike
[…] I only put the code to select the rectangle area – saving that rectangle is the easiest job (right ?) […]
LikeLike
What are the ways to customize the crop box, like if we want an option come along crop box to delete this crop box etc?
LikeLike
For customisation you can add gesture recogniser and do what ever you want like which has been done in the example for zoom-in and zoom-out. Additionally you can have UITapGestureRecognizer
//in constructor
addGestureRecognizer(UITapGestureRecognizer(target: self, action: “ViewTapped:”))
}
//actual implementation
func ViewTapped(tapGR: UITapGestureRecognizer){
// i used delegate to get notified from my main view
if(delegate != nil){
delegate?.Tapped(“propic”)
}
print(“Tapped !!! “)
}
// now the protocol
protocol CustomViewTapped{
func Tapped(sender:String)
}
To remove the crop box from the view we can simply call this method –
.removeFromSuperview().
Hope this helped 🙂
LikeLike
Thanks for this tutorial 🙂
LikeLike
Thank You
LikeLike
Source code for this?
LikeLike
Most of the codes are inside the blog post itself. I did it in a R&D manner – so there is no project which will match the same. I will compile one project and share the code
LikeLike