123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- import UIKit
- import AVFoundation
- open class CropView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelegate, CropRectViewDelegate {
- open var image: UIImage? {
- didSet {
- if image != nil {
- imageSize = image!.size
- }
- imageView?.removeFromSuperview()
- imageView = nil
- zoomingView?.removeFromSuperview()
- zoomingView = nil
- setNeedsLayout()
- }
- }
- open var imageView: UIView? {
- didSet {
- if let view = imageView , image == nil {
- imageSize = view.frame.size
- }
- usingCustomImageView = true
- setNeedsLayout()
- }
- }
- open var croppedImage: UIImage? {
- return image?.rotatedImageWithTransform(rotation, croppedToRect: zoomedCropRect())
- }
- open var keepAspectRatio = false {
- didSet {
- cropRectView.keepAspectRatio = keepAspectRatio
- }
- }
- open var cropAspectRatio: CGFloat {
- set {
- setCropAspectRatio(newValue, shouldCenter: true)
- }
- get {
- let rect = scrollView.frame
- let width = rect.width
- let height = rect.height
- return width / height
- }
- }
- open var rotation: CGAffineTransform {
- guard let imgView = imageView else {
- return CGAffineTransform.identity
- }
- return imgView.transform
- }
- open var rotationAngle: CGFloat {
- set {
- imageView?.transform = CGAffineTransform(rotationAngle: newValue)
- }
- get {
- return atan2(rotation.b, rotation.a)
- }
- }
- open var cropRect: CGRect {
- set {
- zoomToCropRect(newValue)
- }
- get {
- return scrollView.frame
- }
- }
- open var imageCropRect = CGRect.zero {
- didSet {
- resetCropRect()
-
- let scale = min(scrollView.frame.width / imageSize.width, scrollView.frame.height / imageSize.height)
- let x = imageCropRect.minX * scale + scrollView.frame.minX
- let y = imageCropRect.minY * scale + scrollView.frame.minY
- let width = imageCropRect.width * scale
- let height = imageCropRect.height * scale
-
- let rect = CGRect(x: x, y: y, width: width, height: height)
- let intersection = rect.intersection(scrollView.frame)
-
- if !intersection.isNull {
- cropRect = intersection
- }
- }
- }
- open var resizeEnabled = true {
- didSet {
- cropRectView.enableResizing(resizeEnabled)
- }
- }
- open var showCroppedArea = true {
- didSet {
- layoutIfNeeded()
- scrollView.clipsToBounds = !showCroppedArea
- showOverlayView(showCroppedArea)
- }
- }
- open var rotationGestureRecognizer: UIRotationGestureRecognizer!
- fileprivate var imageSize = CGSize(width: 1.0, height: 1.0)
- fileprivate var scrollView: UIScrollView!
- fileprivate var zoomingView: UIView?
- fileprivate let cropRectView = CropRectView()
- fileprivate let topOverlayView = UIView()
- fileprivate let leftOverlayView = UIView()
- fileprivate let rightOverlayView = UIView()
- fileprivate let bottomOverlayView = UIView()
- fileprivate var insetRect = CGRect.zero
- fileprivate var editingRect = CGRect.zero
- fileprivate var interfaceOrientation = UIApplication.shared.statusBarOrientation
- fileprivate var resizing = false
- fileprivate var usingCustomImageView = false
- fileprivate let MarginTop: CGFloat = 37.0
- fileprivate let MarginLeft: CGFloat = 20.0
- public override init(frame: CGRect) {
- super.init(frame: frame)
- initialize()
- }
-
- public required init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
- initialize()
- }
- fileprivate func initialize() {
- autoresizingMask = [.flexibleWidth, .flexibleHeight]
- backgroundColor = UIColor.clear
-
- scrollView = UIScrollView(frame: bounds)
- scrollView.delegate = self
- scrollView.autoresizingMask = [.flexibleTopMargin, .flexibleLeftMargin, .flexibleBottomMargin, .flexibleRightMargin]
- scrollView.backgroundColor = UIColor.clear
- scrollView.maximumZoomScale = 20.0
- scrollView.minimumZoomScale = 1.0
- scrollView.showsHorizontalScrollIndicator = false
- scrollView.showsVerticalScrollIndicator = false
- scrollView.bounces = false
- scrollView.bouncesZoom = false
- scrollView.clipsToBounds = false
- addSubview(scrollView)
-
- rotationGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(CropView.handleRotation(_:)))
- rotationGestureRecognizer?.delegate = self
- scrollView.addGestureRecognizer(rotationGestureRecognizer)
-
- cropRectView.delegate = self
- addSubview(cropRectView)
-
- showOverlayView(showCroppedArea)
- addSubview(topOverlayView)
- addSubview(leftOverlayView)
- addSubview(rightOverlayView)
- addSubview(bottomOverlayView)
- }
-
- open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
- if !isUserInteractionEnabled {
- return nil
- }
-
- if let hitView = cropRectView.hitTest(convert(point, to: cropRectView), with: event) {
- return hitView
- }
- let locationInImageView = convert(point, to: zoomingView)
- let zoomedPoint = CGPoint(x: locationInImageView.x * scrollView.zoomScale, y: locationInImageView.y * scrollView.zoomScale)
- if zoomingView!.frame.contains(zoomedPoint) {
- return scrollView
- }
- return super.hitTest(point, with: event)
- }
-
- open override func layoutSubviews() {
- super.layoutSubviews()
- let interfaceOrientation = UIApplication.shared.statusBarOrientation
-
- if image == nil && imageView == nil {
- return
- }
-
- setupEditingRect()
- if imageView == nil {
- if interfaceOrientation.isPortrait {
- insetRect = bounds.insetBy(dx: MarginLeft, dy: MarginTop)
- } else {
- insetRect = bounds.insetBy(dx: MarginLeft, dy: MarginLeft)
- }
- if !showCroppedArea {
- insetRect = editingRect
- }
- setupZoomingView()
- setupImageView()
- } else if usingCustomImageView {
- if interfaceOrientation.isPortrait {
- insetRect = bounds.insetBy(dx: MarginLeft, dy: MarginTop)
- } else {
- insetRect = bounds.insetBy(dx: MarginLeft, dy: MarginLeft)
- }
- if !showCroppedArea {
- insetRect = editingRect
- }
- setupZoomingView()
- imageView?.frame = zoomingView!.bounds
- zoomingView?.addSubview(imageView!)
- usingCustomImageView = false
- }
-
- if !resizing {
- layoutCropRectViewWithCropRect(scrollView.frame)
- if self.interfaceOrientation != interfaceOrientation {
- zoomToCropRect(scrollView.frame)
- }
- }
-
-
- self.interfaceOrientation = interfaceOrientation
- }
-
- open func setRotationAngle(_ rotationAngle: CGFloat, snap: Bool) {
- var rotation = rotationAngle
- if snap {
- rotation = nearbyint(rotationAngle / CGFloat(Double.pi/2)) * CGFloat(Double.pi/2)
- }
- self.rotationAngle = rotation
- }
-
- open func resetCropRect() {
- resetCropRectAnimated(false)
- }
-
- open func resetCropRectAnimated(_ animated: Bool) {
- if animated {
- UIView.beginAnimations(nil, context: nil)
- UIView.setAnimationDuration(0.25)
- UIView.setAnimationBeginsFromCurrentState(true)
- }
- imageView?.transform = CGAffineTransform.identity
- let contentSize = scrollView.contentSize
- let initialRect = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)
- scrollView.zoom(to: initialRect, animated: false)
-
- layoutCropRectViewWithCropRect(scrollView.bounds)
-
- if animated {
- UIView.commitAnimations()
- }
- }
-
- open func zoomedCropRect() -> CGRect {
- let cropRect = convert(scrollView.frame, to: zoomingView)
- var ratio: CGFloat = 1.0
- let orientation = UIApplication.shared.statusBarOrientation
- if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad || orientation.isPortrait) {
- ratio = AVMakeRect(aspectRatio: imageSize, insideRect: insetRect).width / imageSize.width
- } else {
- ratio = AVMakeRect(aspectRatio: imageSize, insideRect: insetRect).height / imageSize.height
- }
-
- let zoomedCropRect = CGRect(x: cropRect.origin.x / ratio,
- y: cropRect.origin.y / ratio,
- width: cropRect.size.width / ratio,
- height: cropRect.size.height / ratio)
-
- return zoomedCropRect
- }
-
- open func croppedImage(_ image: UIImage) -> UIImage {
- imageSize = image.size
- return image.rotatedImageWithTransform(rotation, croppedToRect: zoomedCropRect())
- }
-
- @objc func handleRotation(_ gestureRecognizer: UIRotationGestureRecognizer) {
- if let imageView = imageView {
- let rotation = gestureRecognizer.rotation
- let transform = imageView.transform.rotated(by: rotation)
- imageView.transform = transform
- gestureRecognizer.rotation = 0.0
- }
-
- switch gestureRecognizer.state {
- case .began, .changed:
- cropRectView.showsGridMinor = true
- default:
- cropRectView.showsGridMinor = false
- }
- }
-
-
- fileprivate func showOverlayView(_ show: Bool) {
- let color = show ? UIColor(white: 0.0, alpha: 0.4) : UIColor.clear
-
- topOverlayView.backgroundColor = color
- leftOverlayView.backgroundColor = color
- rightOverlayView.backgroundColor = color
- bottomOverlayView.backgroundColor = color
- }
-
- fileprivate func setupEditingRect() {
- let interfaceOrientation = UIApplication.shared.statusBarOrientation
- if interfaceOrientation.isPortrait {
- editingRect = bounds.insetBy(dx: MarginLeft, dy: MarginTop)
- } else {
- editingRect = bounds.insetBy(dx: MarginLeft, dy: MarginLeft)
- }
- if !showCroppedArea {
- editingRect = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height)
- }
- }
-
- fileprivate func setupZoomingView() {
- let cropRect = AVMakeRect(aspectRatio: imageSize, insideRect: insetRect)
-
- scrollView.frame = cropRect
- scrollView.contentSize = cropRect.size
-
- zoomingView = UIView(frame: scrollView.bounds)
- zoomingView?.backgroundColor = .clear
- scrollView.addSubview(zoomingView!)
- }
- fileprivate func setupImageView() {
- let imageView = UIImageView(frame: zoomingView!.bounds)
- imageView.backgroundColor = .clear
- imageView.contentMode = .scaleAspectFit
- imageView.image = image
- zoomingView?.addSubview(imageView)
- self.imageView = imageView
- usingCustomImageView = false
- }
-
- fileprivate func layoutCropRectViewWithCropRect(_ cropRect: CGRect) {
- cropRectView.frame = cropRect
- layoutOverlayViewsWithCropRect(cropRect)
- }
-
- fileprivate func layoutOverlayViewsWithCropRect(_ cropRect: CGRect) {
- topOverlayView.frame = CGRect(x: 0, y: 0, width: bounds.width, height: cropRect.minY)
- leftOverlayView.frame = CGRect(x: 0, y: cropRect.minY, width: cropRect.minX, height: cropRect.height)
- rightOverlayView.frame = CGRect(x: cropRect.maxX, y: cropRect.minY, width: bounds.width - cropRect.maxX, height: cropRect.height)
- bottomOverlayView.frame = CGRect(x: 0, y: cropRect.maxY, width: bounds.width, height: bounds.height - cropRect.maxY)
- }
-
- fileprivate func zoomToCropRect(_ toRect: CGRect) {
- zoomToCropRect(toRect, shouldCenter: false, animated: true)
- }
-
- fileprivate func zoomToCropRect(_ toRect: CGRect, shouldCenter: Bool, animated: Bool, completion: (() -> Void)? = nil) {
- if scrollView.frame.equalTo(toRect) {
- return
- }
-
- let width = toRect.width
- let height = toRect.height
- let scale = min(editingRect.width / width, editingRect.height / height)
-
- let scaledWidth = width * scale
- let scaledHeight = height * scale
- let cropRect = CGRect(x: (bounds.width - scaledWidth) / 2.0, y: (bounds.height - scaledHeight) / 2.0, width: scaledWidth, height: scaledHeight)
-
- var zoomRect = convert(toRect, to: zoomingView)
- zoomRect.size.width = cropRect.width / (scrollView.zoomScale * scale)
- zoomRect.size.height = cropRect.height / (scrollView.zoomScale * scale)
-
- if let imgView = imageView , shouldCenter {
- let imageViewBounds = imgView.bounds
- zoomRect.origin.x = (imageViewBounds.width / 2.0) - (zoomRect.width / 2.0)
- zoomRect.origin.y = (imageViewBounds.height / 2.0) - (zoomRect.height / 2.0)
- }
-
- var duration = 0.0
- if animated {
- duration = 0.25
- }
-
- UIView.animate(withDuration: duration, delay: 0.0, options: .beginFromCurrentState, animations: { [unowned self] in
- self.scrollView.bounds = cropRect
- self.scrollView.zoom(to: zoomRect, animated: false)
- self.layoutCropRectViewWithCropRect(cropRect)
- }) { finished in
- completion?()
- }
- }
-
- fileprivate func cappedCropRectInImageRectWithCropRectView(_ cropRectView: CropRectView) -> CGRect {
- var cropRect = cropRectView.frame
-
- let rect = convert(cropRect, to: scrollView)
- if rect.minX < zoomingView!.frame.minX {
- cropRect.origin.x = scrollView.convert(zoomingView!.frame, to: self).minX
- let cappedWidth = rect.maxX
- let height = !keepAspectRatio ? cropRect.size.height : cropRect.size.height * (cappedWidth / cropRect.size.width)
- cropRect.size = CGSize(width: cappedWidth, height: height)
- }
-
- if rect.minY < zoomingView!.frame.minY {
- cropRect.origin.y = scrollView.convert(zoomingView!.frame, to: self).minY
- let cappedHeight = rect.maxY
- let width = !keepAspectRatio ? cropRect.size.width : cropRect.size.width * (cappedHeight / cropRect.size.height)
- cropRect.size = CGSize(width: width, height: cappedHeight)
- }
-
- if rect.maxX > zoomingView!.frame.maxX {
- let cappedWidth = scrollView.convert(zoomingView!.frame, to: self).maxX - cropRect.minX
- let height = !keepAspectRatio ? cropRect.size.height : cropRect.size.height * (cappedWidth / cropRect.size.width)
- cropRect.size = CGSize(width: cappedWidth, height: height)
- }
-
- if rect.maxY > zoomingView!.frame.maxY {
- let cappedHeight = scrollView.convert(zoomingView!.frame, to: self).maxY - cropRect.minY
- let width = !keepAspectRatio ? cropRect.size.width : cropRect.size.width * (cappedHeight / cropRect.size.height)
- cropRect.size = CGSize(width: width, height: cappedHeight)
- }
-
- return cropRect
- }
-
- fileprivate func automaticZoomIfEdgeTouched(_ cropRect: CGRect) {
- if cropRect.minX < editingRect.minX - 5.0 ||
- cropRect.maxX > editingRect.maxX + 5.0 ||
- cropRect.minY < editingRect.minY - 5.0 ||
- cropRect.maxY > editingRect.maxY + 5.0 {
- UIView.animate(withDuration: 1.0, delay: 0.0, options: .beginFromCurrentState, animations: { [unowned self] in
- self.zoomToCropRect(self.cropRectView.frame)
- }, completion: nil)
- }
- }
-
- fileprivate func setCropAspectRatio(_ ratio: CGFloat, shouldCenter: Bool) {
- var cropRect = scrollView.frame
- var width = cropRect.width
- var height = cropRect.height
- if ratio <= 1.0 {
- width = height * ratio
- if width > imageView!.bounds.width {
- width = cropRect.width
- height = width / ratio
- }
- } else {
- height = width / ratio
- if height > imageView!.bounds.height {
- height = cropRect.height
- width = height * ratio
- }
- }
- cropRect.size = CGSize(width: width, height: height)
- zoomToCropRect(cropRect, shouldCenter: shouldCenter, animated: false) {
- let scale = self.scrollView.zoomScale
- self.scrollView.minimumZoomScale = scale
- }
- }
-
-
- func cropRectViewDidBeginEditing(_ view: CropRectView) {
- resizing = true
- }
-
- func cropRectViewDidChange(_ view: CropRectView) {
- let cropRect = cappedCropRectInImageRectWithCropRectView(view)
- layoutCropRectViewWithCropRect(cropRect)
- automaticZoomIfEdgeTouched(cropRect)
- }
-
- func cropRectViewDidEndEditing(_ view: CropRectView) {
- resizing = false
- zoomToCropRect(cropRectView.frame)
- }
-
-
- open func viewForZooming(in scrollView: UIScrollView) -> UIView? {
- return zoomingView
- }
-
- open func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
- let contentOffset = scrollView.contentOffset
- targetContentOffset.pointee = contentOffset
- }
-
-
- open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
- return true
- }
- }
|