Repository: artemkrachulov/AKImageCropperView Branch: master Commit: 3436f26c9da5 Files: 47 Total size: 197.2 KB Directory structure: gitextract_x4b46xu8/ ├── .gitignore ├── AKImageCropperView/ │ ├── AKImageCropperOverlayView.swift │ ├── AKImageCropperOverlayViewConfiguration.swift │ ├── AKImageCropperOverlayViewConfigurationCorner.swift │ ├── AKImageCropperOverlayViewConfigurationEdge.swift │ ├── AKImageCropperOverlayViewConfigurationGrid.swift │ ├── AKImageCropperOverlayViewConfigurationOverlay.swift │ ├── AKImageCropperOverlayViewTouchState.swift │ ├── AKImageCropperScrollView.swift │ ├── AKImageCropperView.h │ ├── AKImageCropperView.swift │ ├── IC_CGFloatExtension.swift │ ├── IC_CGPointExtension.swift │ ├── IC_CGSizeExtensions.swift │ ├── IC_UIImageExtensions.swift │ ├── Info.plist │ └── PrimaryFilledButton.swift ├── AKImageCropperView.podspec ├── AKImageCropperView.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── AKImageCropperDemo.xcscmblueprint │ │ └── Demo.xcscmblueprint │ └── xcshareddata/ │ └── xcschemes/ │ └── AKImageCropperView.xcscheme ├── AKImageCropperViewExample/ │ ├── AppDelegate.swift │ ├── Base.lproj/ │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Constants.swift │ ├── CropperViewController.swift │ ├── CustomImageCropperOverlayView.swift │ ├── HomeViewController.swift │ ├── ImageViewController.swift │ ├── Images.xcassets/ │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Attractive-girl.imageset/ │ │ │ └── Contents.json │ │ ├── Autumn-background.imageset/ │ │ │ └── Contents.json │ │ ├── Colorful-pillows.imageset/ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Cupcakes.imageset/ │ │ │ └── Contents.json │ │ ├── Funnel-cake-stand.imageset/ │ │ │ └── Contents.json │ │ ├── Icons/ │ │ │ ├── Contents.json │ │ │ ├── overlay.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── random.imageset/ │ │ │ │ └── Contents.json │ │ │ └── rotate.imageset/ │ │ │ └── Contents.json │ │ └── Image-of-earth.imageset/ │ │ └── Contents.json │ ├── ImagesTableViewController.swift │ └── Info.plist ├── LICENSE └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode .DS_Store */build/* *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout profile *.moved-aside DerivedData .idea/ *.hmap #CocoaPods Pods Podfile.lock ================================================ FILE: AKImageCropperView/AKImageCropperOverlayView.swift ================================================ // // AKImageCropperOverlayView.swift // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software // and associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // import UIKit protocol AKImageCropperOverlayViewDelegate : class { func cropperOverlayViewDidChangeCropRect(_ view: AKImageCropperOverlayView, _ cropRect: CGRect) } open class AKImageCropperOverlayView: UIView { // MARK: - // MARK: ** Properties ** /** Configuration structure for the Overlay View appearance and behavior. */ open var configuraiton = AKImageCropperCropViewConfiguration() /** Crop rectangle */ internal var cropRect: CGRect = .zero /** Saved crop rectangle state */ fileprivate var touchesBegan: (touch: CGPoint, cropRect: CGRect)! /** Current active crop area part */ fileprivate var activeCropAreaPart: AKCropAreaPart = .none { didSet { layoutSubviews() } } fileprivate struct AKCropAreaPart: OptionSet { public let rawValue: Int public init(rawValue: Int) { self.rawValue = rawValue } static let none = AKCropAreaPart(rawValue: 1 << 0) static let topEdge = AKCropAreaPart(rawValue: 1 << 1) static let leftEdge = AKCropAreaPart(rawValue: 1 << 2) static let bottomEdge = AKCropAreaPart(rawValue: 1 << 3) static let rightEdge = AKCropAreaPart(rawValue: 1 << 4) static let all: AKCropAreaPart = [.topEdge, .rightEdge, .bottomEdge, .leftEdge] static let topLeftCorner: AKCropAreaPart = [.topEdge, .leftEdge] static let topRightCorner: AKCropAreaPart = [.topEdge, .rightEdge] static let bottomRightCorner: AKCropAreaPart = [.bottomEdge, .rightEdge] static let bottomLeftCorner: AKCropAreaPart = [.bottomEdge, .leftEdge] } // MARK: Managing the Delegate weak var delegate: AKImageCropperOverlayViewDelegate? // MARK: Touch & Parts views fileprivate var topcropView: UIView! fileprivate var rightcropView: UIView! fileprivate var bottomcropView: UIView! fileprivate var leftcropView: UIView! fileprivate var topEdgeTouchView: UIView! fileprivate var topEdgeView: UIView! fileprivate var rightEdgeTouchView: UIView! fileprivate var rightEdgeView: UIView! fileprivate var bottomEdgeTouchView: UIView! fileprivate var bottomEdgeView: UIView! fileprivate var leftEdgeTouchView: UIView! fileprivate var leftEdgeView: UIView! fileprivate var topLeftCornerTouchView: UIView! fileprivate var topLeftCornerView: UIView! fileprivate var topRightCornerTouchView: UIView! fileprivate var topRightCornerView: UIView! fileprivate var bottomRightCornerTouchView: UIView! fileprivate var bottomRightCornerView: UIView! fileprivate var bottomLeftCornerTouchView: UIView! fileprivate var bottomLeftCornerView: UIView! fileprivate var gridView: UIView! fileprivate var gridViewVerticalLines: [UIView]! fileprivate var gridViewHorizontalLines: [UIView]! // MARK: - // MARK: ** Initialization OBJECTS(VIEWS) & theirs parameters ** /** Parent (main) class to translate some properties and objects. */ weak var cropperView: AKImageCropperView! fileprivate (set) lazy var overlayView: UIView! = { let view = UIView() view.backgroundColor = UIColor.black.withAlphaComponent(0.5) view.clipsToBounds = true return view }() fileprivate lazy var containerImageView: UIView! = { let view = UIView() view.backgroundColor = UIColor.clear view.clipsToBounds = true view.isUserInteractionEnabled = false return view }() fileprivate lazy var imageView: UIImageView! = { let view = UIImageView() view.backgroundColor = UIColor.clear return view }() open var image: UIImage! { didSet { imageView.image = image } } // MARK: - Initialization /** Returns an overlay view initialized with the specified configuraiton. - Parameter configuraiton: Configuration structure for the Overlay View appearance and behavior. */ init() { super.init(frame: .zero) backgroundColor = .clear alpha = 0 initialize() } public init(configuraiton: AKImageCropperCropViewConfiguration? = nil) { super.init(frame: CGRect.zero) if configuraiton != nil { self.configuraiton = configuraiton! } backgroundColor = UIColor.clear alpha = 0 initialize() } required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - Draving Crop rect frame fileprivate func initialize() { /* Create views layout. Step by step 1. OverlayView */ addSubview(overlayView) let blurEffect = UIBlurEffect(style: configuraiton.overlay.blurStyle) let blurEffectView = UIVisualEffectView(effect: blurEffect) blurEffectView.alpha = self.configuraiton.overlay.blurAlpha blurEffectView.frame = overlayView.bounds blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] overlayView.addSubview(blurEffectView) /* 2. Container view ‹‹ Image view */ containerImageView.addSubview(imageView) addSubview(containerImageView) /* 3. Crop rectangle */ // Edges topEdgeTouchView = UIView() addSubview(topEdgeTouchView) topEdgeView = UIView() topEdgeTouchView.addSubview(topEdgeView) rightEdgeTouchView = UIView() addSubview(rightEdgeTouchView) rightEdgeView = UIView() rightEdgeTouchView.addSubview(rightEdgeView) bottomEdgeTouchView = UIView() addSubview(bottomEdgeTouchView) bottomEdgeView = UIView() bottomEdgeTouchView.addSubview(bottomEdgeView) leftEdgeTouchView = UIView() addSubview(leftEdgeTouchView) leftEdgeView = UIView() leftEdgeTouchView.addSubview(leftEdgeView) if configuraiton.edge.isHidden { topEdgeView.isHidden = true rightEdgeView.isHidden = true bottomEdgeView.isHidden = true leftEdgeView.isHidden = true } // Corners topLeftCornerTouchView = UIView() addSubview(topLeftCornerTouchView) topLeftCornerView = UIView() topLeftCornerView.layer.addSublayer(CAShapeLayer()) topLeftCornerTouchView.addSubview(topLeftCornerView) topRightCornerTouchView = UIView() addSubview(topRightCornerTouchView) topRightCornerView = UIView() topRightCornerView.layer.addSublayer(CAShapeLayer()) topRightCornerTouchView.addSubview(topRightCornerView) bottomRightCornerTouchView = UIView() addSubview(bottomRightCornerTouchView) bottomRightCornerView = UIView() bottomRightCornerView.layer.addSublayer(CAShapeLayer()) bottomRightCornerTouchView.addSubview(bottomRightCornerView) bottomLeftCornerTouchView = UIView() addSubview(bottomLeftCornerTouchView) bottomLeftCornerView = UIView() bottomLeftCornerView.layer.addSublayer(CAShapeLayer()) bottomLeftCornerTouchView.addSubview(bottomLeftCornerView) if configuraiton.corner.isHidden { topLeftCornerView.isHidden = true topRightCornerView.isHidden = true bottomRightCornerView.isHidden = true bottomLeftCornerView.isHidden = true } // Grid gridView = UIView() gridViewVerticalLines = [] gridViewHorizontalLines = [] for _ in 0.. AKCropAreaPart { if cropAreaTopEdgeFrame.contains(point) { return .topEdge } else if cropAreaBottomEdgeFrame.contains(point) { return .bottomEdge } else if cropAreaRightEdgeFrame.contains(point) { return .rightEdge } else if cropAreaLeftEdgeFrame.contains(point) { return .leftEdge } else if cropAreaTopLeftCornerFrame.contains(point) { return .topLeftCorner } else if cropAreaTopRightCornerFrame.contains(point) { return .topRightCorner } else if cropAreaBottomLeftCornerFrame.contains(point) { return .bottomLeftCorner } else if cropAreaBottomRightCornerFrame.contains(point) { return .bottomRightCorner } else { return .none } } // MARK: Other methods final func showOverlayBlur(_ show: Bool, completion: ((Bool) -> Void)? = nil) { UIView.animate(withDuration: configuraiton.animation.duration, delay: 0, options: [], animations: { self.overlayView.subviews.first?.alpha = show ? self.configuraiton.overlay.blurAlpha : 0.0 }, completion: { isComplete in completion?(isComplete) }) } final func showGrid(_ show: Bool, completion: ((Bool) -> Void)? = nil) { if configuraiton.grid.alwaysShowGrid { completion?(true) return } let animations: () -> Void = { _ in self.gridView.alpha = show ? 1 : 0 } if configuraiton.animation.duration == 0 { animations() } else { UIView.animate(withDuration: configuraiton.animation.duration, delay: 0, options: [], animations: animations, completion: { isComplete in completion?(isComplete) }) } } /** Visual representation for top edge view in current user interaction state. - Parameter view: Top edge view. - Parameter touchView: Touch area view where added top edge view. - Parameter state: User interaction state. */ open func layoutTopEdgeView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { var color: UIColor var width: CGFloat if state == .normal { color = configuraiton.edge.normalLineColor width = configuraiton.edge.normalLineWidth } else { color = configuraiton.edge.highlightedLineColor width = configuraiton.edge.highlightedLineWidth } view.backgroundColor = color view.frame = CGRect( x : touchView.bounds.origin.x - configuraiton.cornerTouchSize.width / 2 - configuraiton.edge.normalLineWidth, y : touchView.bounds.midY - width, width : touchView.bounds.size.width + configuraiton.cornerTouchSize.width + configuraiton.edge.normalLineWidth * 2, height : width) } /** Visual representation for right edge view in current user interaction state. - Parameter view: Right edge view. - Parameter touchView: Touch area view where added right edge view. - Parameter state: User interaction state. */ open func layoutRightEdgeView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { var color: UIColor var width: CGFloat if state == .normal { color = configuraiton.edge.normalLineColor width = configuraiton.edge.normalLineWidth } else { color = configuraiton.edge.highlightedLineColor width = configuraiton.edge.highlightedLineWidth } view.backgroundColor = color view.frame = CGRect( x : touchView.bounds.midX, y : touchView.bounds.origin.y - configuraiton.cornerTouchSize.height / 2 - configuraiton.edge.normalLineWidth, width : width, height : touchView.bounds.size.height + configuraiton.cornerTouchSize.height + configuraiton.edge.normalLineWidth * 2) } /** Visual representation for bottom edge view in current user interaction state. - Parameter view: Bottom edge view. - Parameter touchView: Touch area view where added bottom edge view. - Parameter state: User interaction state. */ open func layoutBottomEdgeView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { var color: UIColor var width: CGFloat if state == .normal { color = configuraiton.edge.normalLineColor width = configuraiton.edge.normalLineWidth } else { color = configuraiton.edge.highlightedLineColor width = configuraiton.edge.highlightedLineWidth } view.backgroundColor = color view.frame = CGRect( x : touchView.bounds.origin.x - configuraiton.cornerTouchSize.width / 2 - configuraiton.edge.normalLineWidth, y : touchView.bounds.midY, width : touchView.bounds.size.width + configuraiton.cornerTouchSize.width + configuraiton.edge.normalLineWidth * 2, height : width) } /** Visual representation for left edge view in current user interaction state. - Parameter view: Left edge view. - Parameter touchView: Touch area view where added left edge view. - Parameter state: User interaction state. */ open func layoutLeftEdgeView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { var color: UIColor var width: CGFloat if state == .normal { color = configuraiton.edge.normalLineColor width = configuraiton.edge.normalLineWidth } else { color = configuraiton.edge.highlightedLineColor width = configuraiton.edge.highlightedLineWidth } view.backgroundColor = color view.frame = CGRect( x : touchView.bounds.midX - width, y : touchView.bounds.origin.y - configuraiton.cornerTouchSize.height / 2 - configuraiton.edge.normalLineWidth, width : width, height : touchView.bounds.size.height + configuraiton.cornerTouchSize.height + configuraiton.edge.normalLineWidth * 2) } /** Visual representation for top left corner view in current user interaction state. Drawing going with added shape layer. - Parameter view: Top left corner view. - Parameter touchView: Touch area view where added top left edge view. - Parameter state: User interaction state. */ open func layoutTopLeftCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { var lineWidth: CGFloat let layer: CAShapeLayer = view.layer.sublayers!.first as! CAShapeLayer if state == .normal { layer.fillColor = configuraiton.corner.normalLineColor.cgColor view.frame.size = configuraiton.corner.normaSize lineWidth = configuraiton.corner.normalLineWidth } else { layer.fillColor = configuraiton.edge.highlightedLineColor.cgColor view.frame.size = configuraiton.corner.highlightedSize lineWidth = configuraiton.corner.highlightedLineWidth } view.center = CGPoint(x: touchView.bounds.midX, y: touchView.bounds.midY) let rect = CGRect(origin: CGPoint(x: view.bounds.midX - lineWidth, y: view.bounds.midY - lineWidth), size: view.frame.size) let substractRect = CGRect( x : rect.origin.x + lineWidth, y : rect.origin.y + lineWidth, width : rect.size.width - lineWidth, height : rect.size.height - lineWidth) let path = UIBezierPath(rect: rect) path.append(UIBezierPath(rect: substractRect).reversing()) layer.path = path.cgPath } /** Visual representation for top right corner view in current user interaction state. Drawing going with added shape layer. - Parameter view: Top right corner view. - Parameter touchView: Touch area view where added top right edge view. - Parameter state: User interaction state. */ open func layoutTopRightCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { var lineWidth: CGFloat let layer: CAShapeLayer = view.layer.sublayers!.first as! CAShapeLayer if state == .normal { layer.fillColor = configuraiton.corner.normalLineColor.cgColor view.frame.size = configuraiton.corner.normaSize lineWidth = configuraiton.corner.normalLineWidth } else { layer.fillColor = configuraiton.edge.highlightedLineColor.cgColor view.frame.size = configuraiton.corner.highlightedSize lineWidth = configuraiton.corner.highlightedLineWidth } view.center = CGPoint(x: touchView.bounds.midX, y: touchView.bounds.midY) let rect = CGRect(origin: CGPoint(x: -view.bounds.midX + lineWidth, y: view.bounds.midY - lineWidth), size: view.frame.size) let substractRect = CGRect( x : rect.origin.x, y : rect.origin.y + lineWidth, width : rect.size.width - lineWidth, height : rect.size.height - lineWidth) let path = UIBezierPath(rect: rect) path.append(UIBezierPath(rect: substractRect).reversing()) layer.path = path.cgPath } /** Visual representation for bottom right corner view in current user interaction state. Drawing going with added shape layer. - Parameter view: Bottom right corner view. - Parameter touchView: Touch area view where added bottom right edge view. - Parameter state: User interaction state. */ open func layoutBottomRightCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { var lineWidth: CGFloat let layer: CAShapeLayer = view.layer.sublayers!.first as! CAShapeLayer if state == .normal { layer.fillColor = configuraiton.corner.normalLineColor.cgColor view.frame.size = configuraiton.corner.normaSize lineWidth = configuraiton.corner.normalLineWidth } else { layer.fillColor = configuraiton.edge.highlightedLineColor.cgColor view.frame.size = configuraiton.corner.highlightedSize lineWidth = configuraiton.corner.highlightedLineWidth } view.center = CGPoint(x: touchView.bounds.midX, y: touchView.bounds.midY) let rect = CGRect(origin: CGPoint(x: -view.bounds.midX + lineWidth, y: -view.bounds.midY + lineWidth), size: view.frame.size) let substractRect = CGRect( x : rect.origin.x, y : rect.origin.y, width : rect.size.width - lineWidth, height : rect.size.height - lineWidth) let path = UIBezierPath(rect: rect) path.append(UIBezierPath(rect: substractRect).reversing()) layer.path = path.cgPath } /** Visual representation for bottom left corner view in current user interaction state. Drawing going with added shape layer. - Parameter view: Bottom left corner view. - Parameter touchView: Touch area view where added bottom left edge view. - Parameter state: User interaction state. */ open func layoutBottomLeftCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { var lineWidth: CGFloat let layer: CAShapeLayer = view.layer.sublayers!.first as! CAShapeLayer if state == .normal { layer.fillColor = configuraiton.corner.normalLineColor.cgColor view.frame.size = configuraiton.corner.normaSize lineWidth = configuraiton.corner.normalLineWidth } else { layer.fillColor = configuraiton.edge.highlightedLineColor.cgColor view.frame.size = configuraiton.corner.highlightedSize lineWidth = configuraiton.corner.highlightedLineWidth } view.center = CGPoint(x: touchView.bounds.midX, y: touchView.bounds.midY) let rect = CGRect(origin: CGPoint(x: view.bounds.midX - lineWidth, y: -view.bounds.midY + lineWidth), size: view.frame.size) let substractRect = CGRect( x : rect.origin.x + lineWidth, y : rect.origin.y, width : rect.size.width - lineWidth, height : rect.size.height - lineWidth) let path = UIBezierPath(rect: rect) path.append(UIBezierPath(rect: substractRect).reversing()) layer.path = path.cgPath } /** Visual representation for grid view. - Parameter view: Grid view. - Parameter gridViewHorizontalLines: Horizontal line view`s array. - Parameter gridViewVerticalLines: Vertical line view`s array. */ open func layoutGridView(_ view: UIView, gridViewHorizontalLines: [UIView], gridViewVerticalLines: [UIView]) { for (i, line) in gridViewHorizontalLines.enumerated() { line.frame.origin = CGPoint(x: 0, y: view.frame.height * CGFloat(i + 1) / CGFloat(gridViewHorizontalLines.count + 1)) line.frame.size.width = view.frame.width } for (i, line) in gridViewVerticalLines.enumerated() { line.frame.origin = CGPoint(x: view.frame.width * CGFloat(i + 1) / CGFloat(gridViewVerticalLines.count + 1), y: 0) line.frame.size.height = view.frame.height } } // MARK: - Touches override open func touchesBegan(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } /* Save */ touchesBegan = (touch.location(in: self), cropRect) /* Active part */ activeCropAreaPart = getCropAreaPartContainsPoint(touchesBegan.touch) } override open func touchesMoved(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } /* GET TRANSLATION POINT */ let point = touch.location(in: self) let previousPoint = touch.previousLocation(in: self) let translationPoint = CGPoint(x: point.x - previousPoint.x, y: point.y - previousPoint.y) /* MOVE FRAME */ let cropRectMaxFrame = cropperView.reversedFrameWithInsets if activeCropAreaPart.contains(.topEdge) { cropRect.origin.y += translationPoint.y cropRect.size.height -= translationPoint.y let pointInEdge = touchesBegan.touch.y - touchesBegan.cropRect.minY let minStickPoint = pointInEdge + cropRectMaxFrame.minY let maxStickPoint = pointInEdge + touchesBegan.cropRect.maxY - configuraiton.minCropRectSize.height if point.y > maxStickPoint || cropRect.height < configuraiton.minCropRectSize.height { cropRect.origin.y = touchesBegan.cropRect.maxY - configuraiton.minCropRectSize.height cropRect.size.height = configuraiton.minCropRectSize.height } if point.y < minStickPoint { cropRect.origin.y = cropRectMaxFrame.minY cropRect.size.height = touchesBegan.cropRect.maxY - cropRectMaxFrame.minY } } if activeCropAreaPart.contains(.rightEdge) { cropRect.size.width += translationPoint.x let pointInEdge = touchesBegan.touch.x - touchesBegan.cropRect.maxX let minStickPoint = pointInEdge + touchesBegan.cropRect.minX + configuraiton.minCropRectSize.width let maxStickPoint = pointInEdge + cropRectMaxFrame.maxX if point.x > maxStickPoint { cropRect.size.width = cropRectMaxFrame.maxX - cropRect.origin.x } if point.x < minStickPoint || cropRect.width < configuraiton.minCropRectSize.width { cropRect.size.width = configuraiton.minCropRectSize.width } } if activeCropAreaPart.contains(.bottomEdge) { cropRect.size.height += translationPoint.y let pointInEdge = touchesBegan.touch.y - touchesBegan.cropRect.maxY let minStickPoint = pointInEdge + touchesBegan.cropRect.minY + configuraiton.minCropRectSize.height let maxStickPoint = pointInEdge + cropRectMaxFrame.maxY if point.y > maxStickPoint { cropRect.size.height = cropRectMaxFrame.maxY - cropRect.origin.y } if point.y < minStickPoint || cropRect.height < configuraiton.minCropRectSize.height { cropRect.size.height = configuraiton.minCropRectSize.height } } if activeCropAreaPart.contains(.leftEdge) { cropRect.origin.x += translationPoint.x cropRect.size.width -= translationPoint.x let pointInEdge = touchesBegan.touch.x - touchesBegan.cropRect.minX let minStickPoint = pointInEdge + cropRectMaxFrame.minX let maxStickPoint = pointInEdge + touchesBegan.cropRect.maxX - configuraiton.minCropRectSize.width if point.x > maxStickPoint || cropRect.width < configuraiton.minCropRectSize.width { cropRect.origin.x = touchesBegan.cropRect.maxX - configuraiton.minCropRectSize.width cropRect.size.width = configuraiton.minCropRectSize.width } if point.x < minStickPoint { cropRect.origin.x = cropRectMaxFrame.minX cropRect.size.width = touchesBegan.cropRect.maxX - cropRectMaxFrame.minX } } /* Update UI for the crop rectange */ layoutSubviews() /* Delegates */ delegate?.cropperOverlayViewDidChangeCropRect(self, cropRect) } override open func touchesEnded(_ touches: Set, with event: UIEvent?) { /* Active part */ activeCropAreaPart = .none } // MARK: - Instance Method override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard alpha == 1 else { return cropperView.scrollView } return self.point(inside: point, with: event) && getCropAreaPartContainsPoint(point) != .none ? self : cropperView.scrollView } // MARK: - Match Foreground To Background func matchForegroundToBackgroundScrollViewOffset() { imageView.frame.origin = CGPoint( x: -(cropperView.scrollView.contentOffset.x + containerImageView.frame.origin.x), y: -(cropperView.scrollView.contentOffset.y + containerImageView.frame.origin.y)) } func matchForegroundToBackgroundScrollViewSize() { imageView.frame.size = cropperView.scrollView.contentSize } } ================================================ FILE: AKImageCropperView/AKImageCropperOverlayViewConfiguration.swift ================================================ // // AKImageCropperCropViewConfiguration.swift // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software // and associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // import UIKit /// Overlay view configuration struct. public struct AKImageCropperCropViewConfiguration { public init() {} // MARK: Crop rectangle /// Delay before the crop rectangle will scale to fit cropper view frame edges. public var zoomingToFitDelay: TimeInterval = 1.0 /** Animation options for layout transitions. - duration: The duration of the transition animation, measured in seconds. - options: Specifies the supported animation curves. */ public var animation: (duration: TimeInterval, options: UIViewAnimationOptions) = (duration: 0.3, options: .curveEaseInOut) /// Edges insets for crop rectangle. Static values for programmatically rotation. public var cropRectInsets = UIEdgeInsetsMake(20, 20, 20, 20) /// The smallest value for the crop rectangle sizes. Initial value of this property is 60 pixels width and 60 pixels height. public var minCropRectSize: CGSize = CGSize(width: 60, height: 60) /// Touch view where will be drawn the corner. public var cornerTouchSize: CGSize = CGSize(width: 30.0, height: 30.0) /** Thickness for edges touch area. This touch view is centered on the edge line. - vertical: Thickness for vertical edges: Left, Right. - horizontal: Thickness for horizontal edges: Top, Bottom. */ public var edgeTouchThickness: (vertical: CGFloat, horizontal: CGFloat) = (vertical: 20.0, horizontal: 20.0) // MARK: Visual Appearance /// Overlay visual configuration. public var overlay = AKImageCropperCropViewConfigurationOverlay() /// Edges visual configuration. public var edge = AKImageCropperCropViewConfigurationEdge() /// Corners visual configuration. public var corner = AKImageCropperCropViewConfigurationCorner() /// Grid visual configuration. public var grid = AKImageCropperCropViewConfigurationGrid() } ================================================ FILE: AKImageCropperView/AKImageCropperOverlayViewConfigurationCorner.swift ================================================ // // AKImageCropperCropViewConfigurationCorner.swift // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software // and associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // import UIKit /// Corners configuration struct. public struct AKImageCropperCropViewConfigurationCorner { /// A Boolean value that determines whether the corner view is hidden. public var isHidden: Bool = false /// Line width for normal corner state. public var normalLineWidth: CGFloat = 3.0 /// Line width for highlighted corner state. public var highlightedLineWidth: CGFloat = 3.0 /// Size for normal corner state. public var normaSize: CGSize = CGSize(width: 20, height: 20) /// Size for highlighted corner state. public var highlightedSize: CGSize = CGSize(width: 30, height: 30) /// Line color for normal corner state. public var normalLineColor: UIColor = .white /// Line color for highlighted corner state. public var highlightedLineColor: UIColor = .white } ================================================ FILE: AKImageCropperView/AKImageCropperOverlayViewConfigurationEdge.swift ================================================ // // AKImageCropperCropViewConfigurationEdge.swift // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software // and associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // import UIKit /// Edges configuration struct. public struct AKImageCropperCropViewConfigurationEdge { /// A Boolean value that determines whether the edge view is hidden. public var isHidden: Bool = false /// Line width for normal edge state. public var normalLineWidth: CGFloat = 1.0 /// Line width for highlighted edge state. public var highlightedLineWidth: CGFloat = 3.0 /// Line color for normal edge state. public var normalLineColor: UIColor = .white /// Line color for highlighted edge state. public var highlightedLineColor: UIColor = .white } ================================================ FILE: AKImageCropperView/AKImageCropperOverlayViewConfigurationGrid.swift ================================================ // // AKImageCropperCropViewConfigurationGrid.swift // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software // and associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // import UIKit /// Grid visual configuration struct. public struct AKImageCropperCropViewConfigurationGrid { /// A Boolean value that determines whether the edge view is hidden. public var isHidden: Bool = false /// Hide grid after user interaction. public var alwaysShowGrid: Bool = false /** The number of vertical and horizontal lines inside the crop rectangle. - vertical: Vertical lines count. - horizontal: Horizontal lines count. */ public var linesCount: (vertical: Int, horizontal: Int) = (vertical: 2, horizontal: 2) /// Vertical and horizontal lines width. public var linesWidth: CGFloat = 1.0 /// Vertical and horizontal lines color. public var linesColor: UIColor = UIColor.white.withAlphaComponent(0.5) } ================================================ FILE: AKImageCropperView/AKImageCropperOverlayViewConfigurationOverlay.swift ================================================ // // AKImageCropperCropViewConfigurationOverlay.swift // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software // and associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // import UIKit /// Overlay configuration struct. public struct AKImageCropperCropViewConfigurationOverlay { /// The view’s background color. public var backgroundColor: UIColor = UIColor.black.withAlphaComponent(0.5) /// A Boolean value that determines whether the blur effect is enable. /// /// The blur effect added over overlay view. The effect will disappear before user interaction will start. After manipulations, the effect will revert to the initial state. public var isBlurEnabled: Bool = true /// The intensity of the blur effect. public var blurStyle: UIBlurEffectStyle = .dark /// The blur effect alpha value. public var blurAlpha: CGFloat = 0.6 } ================================================ FILE: AKImageCropperView/AKImageCropperOverlayViewTouchState.swift ================================================ // // AKImageCropperCropViewTouchState.swift // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software // and associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // /// User interaction state for edge or corner. public enum AKImageCropperCropViewTouchState { /// Default, relase case normal /// Press, touch, etc. case highlighted } ================================================ FILE: AKImageCropperView/AKImageCropperScrollView.swift ================================================ // // AKImageCropperScrollView.swift // AKImageCropperView // // Created by Artem Krachulov on 12/17/16. // Copyright © 2016 Artem Krachulov. All rights reserved. // import UIKit final class AKImageCropperScrollView: UIScrollView { // MARK: - // MARK: ** Properties ** /** Return visible rect of an UIScrollView's content */ open var visibleRect: CGRect { return CGRect( x : contentInset.left, y : contentInset.top, width : frame.size.width - contentInset.left - contentInset.right, height : frame.size.height - contentInset.top - contentInset.bottom) } /** Returns scaled visible rect of an UIScrollView's content */ open var scaledVisibleRect: CGRect { return CGRect( x : (contentOffset.x + contentInset.left) / zoomScale, y : (contentOffset.y + contentInset.top) / zoomScale, width : visibleRect.size.width / zoomScale, height : visibleRect.size.height / zoomScale) } // MARK: - // MARK: ** Initialization OBJECTS(VIEWS) & theirs parameters ** fileprivate lazy var imageView: UIImageView! = { let view = UIImageView() return view }() open var image: UIImage! { didSet { /* Prepare scroll view to changing the image */ maximumZoomScale = 1 minimumZoomScale = 1 zoomScale = 1 /* Update an image view */ imageView.image = image imageView.frame.size = image.size contentSize = image.size } } // MARK: - Initialization /** Returns an scroll view initialized object. */ init() { super.init(frame: .zero) alwaysBounceVertical = true alwaysBounceHorizontal = true showsVerticalScrollIndicator = false showsHorizontalScrollIndicator = false addSubview(imageView) } required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: AKImageCropperView/AKImageCropperView.h ================================================ // // AKImageCropperView.h // AKImageCropperView // // Created by Artem Krachulov on 12/13/16. // Copyright © 2016 Artem Krachulov. All rights reserved. // #import //! Project version number for AKImageCropperView. FOUNDATION_EXPORT double AKImageCropperViewVersionNumber; //! Project version string for AKImageCropperView. FOUNDATION_EXPORT const unsigned char AKImageCropperViewVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import ================================================ FILE: AKImageCropperView/AKImageCropperView.swift ================================================ // // AKImageCropperView.swift // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software // and associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // import UIKit typealias CGPointPercentage = CGPoint open class AKImageCropperView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelegate, AKImageCropperOverlayViewDelegate { /** Current rotation angle */ fileprivate var angle: Double = 0.0 /** Scroll view minimum edge insets (current value) */ fileprivate var minEdgeInsets: UIEdgeInsets = .zero /** Reversed frame direct to current rotation angle */ fileprivate var reversedRect: CGRect { return CGRect( origin : .zero, size : ((angle / M_PI_2).truncatingRemainder(dividingBy: 2)) == 1 ? CGSize(width: frame.size.height, height: frame.size.width) : frame.size) } /** Reversed minimum edge insets direct to current rotation angle */ fileprivate var reversedEdgeInsets: UIEdgeInsets { var newEdgeInsets: UIEdgeInsets switch angle { case M_PI_2: newEdgeInsets = UIEdgeInsetsMake( minEdgeInsets.right, minEdgeInsets.top, minEdgeInsets.left, minEdgeInsets.bottom) case M_PI: newEdgeInsets = UIEdgeInsetsMake( minEdgeInsets.bottom, minEdgeInsets.right, minEdgeInsets.top, minEdgeInsets.left) case M_PI_2 * 3: newEdgeInsets = UIEdgeInsetsMake( minEdgeInsets.left, minEdgeInsets.bottom, minEdgeInsets.right, minEdgeInsets.top) default: newEdgeInsets = minEdgeInsets } return newEdgeInsets } /** Reversed frame + edgeInsets direct to current rotation angle */ var reversedFrameWithInsets: CGRect { return UIEdgeInsetsInsetRect(reversedRect, reversedEdgeInsets) } // MARK: - // MARK: ** Saved properties ** fileprivate struct SavedProperty { var scaleAspectRatio: CGFloat! var contentOffset: CGPoint! var contentOffsetPercentage: CGPoint! var cropRectSize: CGSize! init() { scaleAspectRatio = 1.0 contentOffset = .zero contentOffsetPercentage = .zero cropRectSize = .zero } mutating func save(scrollView: AKImageCropperScrollView) { scaleAspectRatio = scrollView.zoomScale / scrollView.minimumZoomScale contentOffset = CGPoint( x: scrollView.contentOffset.x + scrollView.contentInset.left, y: scrollView.contentOffset.y + scrollView.contentInset.top) let contentSize = CGSize( width : (scrollView.contentSize.width - scrollView.visibleRect.width).ic_roundTo(precision: 3), height : (scrollView.contentSize.height - scrollView.visibleRect.height).ic_roundTo(precision: 3)) contentOffsetPercentage = CGPointPercentage( x: (contentOffset.x > 0 && contentSize.width != 0) ? ic_round(x: contentOffset.x / contentSize.width, multiplier: 0.005) : 0, y: (contentOffset.y > 0 && contentSize.height != 0) ? ic_round(x: contentOffset.y / contentSize.height, multiplier: 0.005) : 0) cropRectSize = scrollView.visibleRect.size } } /** Saved Scroll View parameters before complex layout animation */ fileprivate var savedProperty = SavedProperty() // MARK: Accessing the Displayed Images /** The image displayed in the image cropper view. Default is nil. */ open var image: UIImage? { didSet { guard let image = image else { return } scrollView.image = image overlayView?.image = image reset() } } /** Cropperd image in the specified crop rectangle */ open var croppedImage: UIImage? { return image?.ic_imageInRect(scrollView.scaledVisibleRect)?.ic_rotateByAngle(angle) } // MARK: States /** Returns the image edited state flag. */ open var isEdited: Bool { guard let image = image else { return false } let fitScaleMultiplier = ic_CGSizeFitScaleMultiplier(image.size, relativeToSize: reversedFrameWithInsets.size) return angle != 0 || fitScaleMultiplier != scrollView.minimumZoomScale || fitScaleMultiplier != scrollView.zoomScale } /** Determines the overlay view current state. Default is false. */ open private(set) var isOverlayViewActive: Bool = false fileprivate var layoutByImage: Bool = true /** Сompletion blocker. */ fileprivate var isAnimation: Bool = false // MARK: Managing the Delegate /** The delegate of the cropper view object. */ weak open var delegate: AKImageCropperViewDelegate? // MARK: - // MARK: ** Initialization OBJECTS(VIEWS) & theirs parameters ** // MARK: Rotate view fileprivate lazy var rotateView: UIView! = { let view = UIView() view.clipsToBounds = true return view }() // MARK: Scroll view var scrollView: AKImageCropperScrollView! // MARK: Overlay Crop view open var overlayView: AKImageCropperOverlayView? { willSet { if overlayView != nil { overlayView?.removeFromSuperview() } if newValue != nil { newValue?.delegate = self newValue?.cropperView = self rotateView.addSubview(newValue!) } layoutSubviews() } } // MARK: - Initializing an Image Cropper View required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) initialize() } override public init(frame: CGRect) { super.init(frame: frame) initialize() } /** Returns an image cropper view initialized with the specified image. - Parameter image: The initial image to display in the image cropper view. */ public init(image: UIImage?) { super.init(frame:CGRect.zero) initialize() self.image = image } fileprivate func initialize() { /* Create views layout. Step by step 1. Scroll view ‹‹ Image view */ scrollView = AKImageCropperScrollView() scrollView.delegate = self rotateView.addSubview(scrollView) /* 2. Overlay view with crop rectangle */ overlayView = AKImageCropperOverlayView() /* 3. Rotate view */ addSubview(rotateView) /** Add Observers To controll all parameters changing and pass them to foreground image view */ initObservers() let pressGesture = UILongPressGestureRecognizer(target: self, action: #selector(pressGestureAction(_ :))) pressGesture.minimumPressDuration = 0 pressGesture.cancelsTouchesInView = false pressGesture.delegate = self addGestureRecognizer(pressGesture) } @objc fileprivate func pressGestureAction(_ gesture: UITapGestureRecognizer) { if gesture.state == .began { beforeInteraction() } else if gesture.state == .cancelled || gesture.state == .ended { afterInteraction() } } // MARK: - UIGestureRecognizerDelegate public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } deinit { removeObservers() #if AKImageCropperViewDEBUG print("deinit AKImageCropperView") #endif } // MARK: - Observe Scroll view values fileprivate func initObservers() { for forKeyPath in ["contentOffset", "contentSize"] { scrollView.addObserver(self, forKeyPath: forKeyPath, options: [.new, .old], context: nil) } } open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard let keyPath = keyPath else { return } switch keyPath { case "contentOffset": if let _ = change![NSKeyValueChangeKey.newKey] { overlayView?.matchForegroundToBackgroundScrollViewOffset() } case "contentSize": if let _ = change![NSKeyValueChangeKey.newKey] { overlayView?.matchForegroundToBackgroundScrollViewSize() } default: () } } fileprivate func removeObservers() { for forKeyPath in ["contentOffset", "contentSize"] { scrollView.removeObserver(self, forKeyPath: forKeyPath) } } // MARK: - Life cycle override open func layoutSubviews() { super.layoutSubviews() layoutSubviews(byImage: layoutByImage) } func layoutSubviews(byImage: Bool) { rotateView.frame = CGRect(origin: .zero, size: self.frame.size) let frame = reversedRect let reversedFrameWithInsetsSize = reversedFrameWithInsets.size if byImage { /* Zoom */ let fitScaleMultiplier = image == nil ? 1 : ic_CGSizeFitScaleMultiplier(image!.size, relativeToSize: reversedFrameWithInsetsSize) scrollView.maximumZoomScale = fitScaleMultiplier * 1000 scrollView.minimumZoomScale = fitScaleMultiplier scrollView.zoomScale = fitScaleMultiplier * savedProperty.scaleAspectRatio } else { /* Zoom */ let fitScaleMultiplier = ic_CGSizeFitScaleMultiplier(scrollView.visibleRect.size, relativeToSize: reversedFrameWithInsetsSize) scrollView.maximumZoomScale *= fitScaleMultiplier scrollView.minimumZoomScale *= fitScaleMultiplier scrollView.zoomScale *= fitScaleMultiplier /* Content inset */ var size: CGSize if overlayView?.alpha == 1 { size = CGSize( width : scrollView.visibleRect.size.width * fitScaleMultiplier, height : scrollView.visibleRect.size.height * fitScaleMultiplier) } else { size = ic_CGSizeFits(scrollView.contentSize, minSize: .zero, maxSize: reversedFrameWithInsetsSize) } scrollView.contentInset = centeredInsets(from: size, to: reversedFrameWithInsetsSize) /* Content offset */ let savedFitScaleMultiplier = ic_CGSizeFitScaleMultiplier(savedProperty.cropRectSize, relativeToSize: reversedFrameWithInsetsSize) var contentOffset = CGPoint( x: savedProperty.contentOffset.x * savedFitScaleMultiplier, y: savedProperty.contentOffset.y * savedFitScaleMultiplier) contentOffset.x -= scrollView.contentInset.left contentOffset.y -= scrollView.contentInset.top scrollView.contentOffset = contentOffset } scrollView.frame = frame overlayView?.frame = frame overlayView?.cropRect = scrollView.visibleRect overlayView?.layoutSubviews() } // MARK: - // MARK: ** Actions ** // MARK: Overlay actions /** Show the overlay view with crop rectangle. - Parameter duration: The total duration of the animations, measured in seconds. If you specify a negative value or 0, the changes are made without animating them. Default value is 0. - Parameter options: A mask of options indicating how you want to perform the animations. For a list of valid constants, see UIViewAnimationOptions. Default value is .curveEaseInOut. - Parameter completion: A block object to be executed when the animation sequence ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called. If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle. This parameter may be NULL. */ open func showOverlayView(animationDuration duration: TimeInterval = 0, options: UIViewAnimationOptions = .curveEaseInOut, completion: ((Bool) -> Void)? = nil) { guard let image = image, let overlayView = overlayView, !isOverlayViewActive && !isAnimation else { return } minEdgeInsets = overlayView.configuraiton.cropRectInsets savedProperty.save(scrollView: scrollView) cancelZoomingTimer() let _animations: () -> Void = { _ in self.layoutSubviews() // Update scroll view content offsets using active zooming scale and insets. self.scrollView.contentOffset = self.contentOffset(from: self.savedProperty.contentOffsetPercentage) } let _completion: (Bool) -> Void = { isFinished in // Update zoom relative to crop rext let fillScaleMultiplier = ic_CGSizeFillScaleMultiplier(image.size, relativeToSize: overlayView.cropRect.size) self.scrollView.maximumZoomScale = fillScaleMultiplier * 1000 self.scrollView.minimumZoomScale = fillScaleMultiplier /* */ self.isAnimation = false self.isOverlayViewActive = true completion?(isFinished) } /* Show */ layoutByImage = false isAnimation = true if duration == 0 { _animations() overlayView.alpha = 1 _completion(true) } else { UIView.animate(withDuration: duration, delay: 0, options: options, animations: _animations, completion: { _ in UIView.animate(withDuration: duration, delay: 0, options: options, animations: { overlayView.alpha = 1 }, completion: _completion) }) } } /** Hide the overlay view with crop rectangle. - Parameter duration: The total duration of the animations, measured in seconds. If you specify a negative value or 0, the changes are made without animating them. Default value is 0. - Parameter options: A mask of options indicating how you want to perform the animations. For a list of valid constants, see UIViewAnimationOptions. Default value is .curveEaseInOut. - Parameter completion: A block object to be executed when the animation sequence ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called. If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle. This parameter may be NULL. */ open func hideOverlayView(animationDuration duration: TimeInterval = 0, options: UIViewAnimationOptions = .curveEaseInOut, completion: ((Bool) -> Void)? = nil) { guard let image = image, let overlayView = overlayView, isOverlayViewActive && !isAnimation else { return } minEdgeInsets = .zero savedProperty.save(scrollView: scrollView) cancelZoomingTimer() isAnimation = true let _animations: () -> Void = { _ in self.layoutSubviews() /** Update scroll view content offsets using active zooming scale and insets. */ self.scrollView.contentOffset = self.contentOffset(from: self.savedProperty.contentOffsetPercentage) } let _completion: (Bool) -> Void = { isFinished in // Update zoom relative to crop rext let fitScaleMultiplier = ic_CGSizeFitScaleMultiplier(image.size, relativeToSize: self.reversedFrameWithInsets.size) self.scrollView.maximumZoomScale = fitScaleMultiplier * 1000 self.scrollView.minimumZoomScale = fitScaleMultiplier /* */ self.isAnimation = false self.isOverlayViewActive = false self.layoutByImage = false completion?(isFinished) } if duration == 0 { overlayView.alpha = 0 _animations() _completion(true) } else { UIView.animate(withDuration: duration, delay: 0, options: options, animations: { overlayView.alpha = 0 }, completion: { _ in UIView.animate(withDuration: duration, delay: 0, options: options, animations: _animations, completion: _completion) }) } } // MARK: Rotate /** Rotate the image on the angle in multiples of 90 degrees (M_PI_2). - Parameter angle: Rotation angle. The angle a multiple of 90 degrees (M_PI_2). - Parameter duration: The total duration of the animations, measured in seconds. If you specify a negative value or 0, the changes are made without animating them. Default value is 0. - Parameter options: A mask of options indicating how you want to perform the animations. For a list of valid constants, see UIViewAnimationOptions. Default value is .curveEaseInOut. - Parameter completion: A block object to be executed when the animation sequence ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called. If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle. This parameter may be NULL. */ open func rotate(_ angle: Double, withDuration duration: TimeInterval = 0, options: UIViewAnimationOptions = .curveEaseInOut, completion: ((Bool) -> Void)? = nil) { guard angle.truncatingRemainder(dividingBy: M_PI_2) == 0 else { return } self.angle = angle savedProperty.save(scrollView: scrollView) let _animations: () -> Void = { _ in self.rotateView.transform = CGAffineTransform(rotationAngle: CGFloat(angle)) self.layoutSubviews() } let _completion: (Bool) -> Void = { isFinished in completion?(isFinished) } if duration == 0 { _animations() _completion(true) } else { UIView.animate(withDuration: duration, delay: 0, options: options, animations: _animations, completion: _completion) } } // MARK: Reset /** Return Cropper view to the initial state. - Parameter duration: The total duration of the animations, measured in seconds. If you specify a negative value or 0, the changes are made without animating them. Default value is 0. - Parameter options: A mask of options indicating how you want to perform the animations. For a list of valid constants, see UIViewAnimationOptions. Default value is .curveEaseInOut. - Parameter completion: A block object to be executed when the animation sequence ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called. If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle. This parameter may be NULL. */ open func reset(animationDuration duration: TimeInterval = 0, options: UIViewAnimationOptions = .curveEaseInOut, completion: ((Bool) -> Void)? = nil) { guard !isAnimation else { return } savedProperty = SavedProperty() angle = 0 cancelZoomingTimer() let _animations: () -> Void = { _ in self.rotateView.transform = CGAffineTransform.identity let _layoutByImage = self.layoutByImage self.layoutByImage = true self.layoutSubviews() self.layoutByImage = _layoutByImage } let _completion: (Bool) -> Void = { isFinished in self.isAnimation = false completion?(isFinished) } isAnimation = true if duration == 0 { _animations() _completion(true) } else { UIView.animate(withDuration: duration, delay: 0, options: options, animations: _animations, completion: _completion) } } // MARK: - Edge insets zooming fileprivate var zoomingTimer: Timer? fileprivate func startZoomingTimer() { guard let overlayView = overlayView else { return } cancelZoomingTimer() zoomingTimer = Timer.scheduledTimer(timeInterval: overlayView.configuraiton.zoomingToFitDelay, target: self, selector: #selector(zoomAction), userInfo: nil, repeats: false) } @objc fileprivate func zoomAction() { guard let overlayView = overlayView else { return } savedProperty.save(scrollView: scrollView) overlayView.showGrid(false) overlayView.showOverlayBlur(true) UIView.animate(withDuration: overlayView.configuraiton.animation.duration, delay: 0, options: overlayView.configuraiton.animation.options, animations: { self.layoutSubviews() }) } fileprivate func cancelZoomingTimer() { zoomingTimer?.invalidate() zoomingTimer = nil } // MARK: - After interaction actions fileprivate func beforeInteraction() { guard let overlayView = overlayView, overlayView.alpha == 1.0 else { return } cancelZoomingTimer() overlayView.showGrid(true) overlayView.showOverlayBlur(false) } fileprivate func afterInteraction() { guard let overlayView = overlayView, overlayView.alpha == 1.0 else { return } startZoomingTimer() savedProperty.save(scrollView: scrollView) } // MARK: - Helper methods private func centeredInsets(from size: CGSize, to relativeSize: CGSize) -> UIEdgeInsets { let center = ic_CGPointCenters(size, relativeToSize: relativeSize) // Fix insets direct to orientation return UIEdgeInsetsMake( center.y + minEdgeInsets.top, center.x + minEdgeInsets.left, center.y + minEdgeInsets.bottom, center.x + minEdgeInsets.right) } private func contentOffset(from savedContentOffsetPercentage: CGPointPercentage) -> CGPoint { var contentOffset = CGPoint( x: scrollView.contentInset.left > minEdgeInsets.left ? 0 : ((scrollView.contentSize.width - reversedFrameWithInsets.size.width) * savedContentOffsetPercentage.x), y: scrollView.contentInset.top > minEdgeInsets.left ? 0 : ((scrollView.contentSize.height - reversedFrameWithInsets.size.height) * savedContentOffsetPercentage.y)) contentOffset.x -= scrollView.contentInset.left contentOffset.y -= scrollView.contentInset.top return contentOffset } // MARK: - AKImageCropperOverlayViewDelegate func cropperOverlayViewDidChangeCropRect(_ view: AKImageCropperOverlayView, _ cropRect: CGRect) { scrollView.contentInset = UIEdgeInsetsMake( cropRect.origin.y, cropRect.origin.x, view.frame.size.height - cropRect.size.height - cropRect.origin.y, view.frame.size.width - cropRect.size.width - cropRect.origin.x) if cropRect.size.height > scrollView.contentSize.height || cropRect.size.width > scrollView.contentSize.width { let fillScaleMultiplier = ic_CGSizeFillScaleMultiplier(scrollView.contentSize, relativeToSize: cropRect.size) scrollView.maximumZoomScale *= fillScaleMultiplier scrollView.minimumZoomScale *= fillScaleMultiplier scrollView.zoomScale *= fillScaleMultiplier } } // MARK: - UIScrollViewDelegate public func viewForZooming(in scrollView: UIScrollView) -> UIView? { return scrollView.subviews.first } public func scrollViewDidZoom(_ scrollView: UIScrollView) { guard layoutByImage else { return } let size = ic_CGSizeFits(scrollView.contentSize, minSize: .zero, maxSize: reversedFrameWithInsets.size) scrollView.contentInset = centeredInsets(from: size, to: reversedFrameWithInsets.size) } public func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { beforeInteraction() } public func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { afterInteraction() } public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { beforeInteraction() } public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { afterInteraction() } } // MARK: - AKImageCropperViewDelegate public protocol AKImageCropperViewDelegate : class { /** Tells the delegate that crop frame was changed. - Parameter view : The image cropper view. - Parameter rect : New crop rectangle origin and size. */ func imageCropperViewDidChangeCropRect(view: AKImageCropperView, cropRect rect: CGRect) } public extension AKImageCropperViewDelegate { func imageCropperViewDidChangeCropRect(view: AKImageCropperView, cropRect rect: CGRect) {} } ================================================ FILE: AKImageCropperView/IC_CGFloatExtension.swift ================================================ // // ic_CGFloatExtension.swift // AKImageCropperView // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // import UIKit extension CGFloat { /** Rounds the value to the nearest with precision. */ public func ic_roundTo(precision: Int) -> CGFloat { let divisor = pow(10.0, CGFloat(precision)) return (self * divisor).rounded() / divisor } } /** Rounds the value to the nearest with multiplier. */ public func ic_round(x: CGFloat, multiplier: CGFloat) -> CGFloat { return multiplier * round(x / multiplier) } ================================================ FILE: AKImageCropperView/IC_CGPointExtension.swift ================================================ // // IC_CGPointExtension.swift // AKImageCropperView // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // import Foundation /** Return centered origin value relative to other size . */ func ic_CGPointCenters(_ size1: CGSize, relativeToSize size2: CGSize) -> CGPoint { return CGPoint(x: size2.width / 2 - size1.width / 2, y: size2.height / 2 - size1.height / 2) } ================================================ FILE: AKImageCropperView/IC_CGSizeExtensions.swift ================================================ // // IC_CGSizeExtensions.swift // AKImageCropperView // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // import Foundation /** Returns fill scale value relative to target size with aspect ratio. */ func ic_CGSizeFitScaleMultiplier(_ size1: CGSize, relativeToSize size2: CGSize) -> CGFloat { guard size1.width != 0 && size1.height != 0 else { return 1.0 } return min(size2.height / size1.height, size2.width / size1.width) } /** Returns fill scale value relative to target size with aspect ratio. */ func ic_CGSizeFillScaleMultiplier(_ size1: CGSize, relativeToSize size2: CGSize) -> CGFloat { guard size1.width != 0 && size1.height != 0 else { return 1.0 } return max(size2.height / size1.height, size2.width / size1.width) } /** Returns size that fits min and max sizes. */ func ic_CGSizeFits(_ size: CGSize, minSize: CGSize, maxSize: CGSize) -> CGSize { var size = size if size.width > maxSize.width { size.width = maxSize.width } if size.height > maxSize.height { size.height = maxSize.height } if size.width < minSize.width { size.width = minSize.width } if size.height < minSize.height { size.height = minSize.height } return size } ================================================ FILE: AKImageCropperView/IC_UIImageExtensions.swift ================================================ // // IC_UIImageExtensions.swift // AKImageCropperView // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // import Foundation extension UIImage { /** Returns image cropped from selected rectangle. */ func ic_imageInRect(_ rect: CGRect) -> UIImage? { UIGraphicsBeginImageContext(rect.size) // Create the bitmap context guard let context = UIGraphicsGetCurrentContext() else { return nil } // Sets the clipping path to the intersection of the current clipping path with the area defined by the specified rectangle. context.clip(to: CGRect(origin: .zero, size: rect.size)) self.draw(in: CGRect(origin: CGPoint(x: -rect.origin.x, y: -rect.origin.y), size: self.size)) // Returns an image based on the contents of the current bitmap-based graphics context. let contextImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return contextImage } /** Returns image rotated by specified angle. */ func ic_rotateByAngle(_ angle: Double) -> UIImage? { // Calculate the size of the rotated view's containing box for our drawing space let rotatedViewBox = UIView(frame: CGRect(origin: .zero, size: self.size)) rotatedViewBox.transform = CGAffineTransform(rotationAngle: CGFloat(angle)) let rotatedSize = rotatedViewBox.frame.size UIGraphicsBeginImageContext(rotatedSize) // Create the bitmap context guard let context = UIGraphicsGetCurrentContext() else { return nil } context.translateBy(x: rotatedSize.width / 2.0, y: rotatedSize.height / 2.0) context.rotate(by: CGFloat(angle)) self.draw(in: CGRect(origin: CGPoint(x: -self.size.width / 2, y: -self.size.height / 2), size: self.size)) // Returns an image based on the contents of the current bitmap-based graphics context. let contextImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return contextImage } } ================================================ FILE: AKImageCropperView/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString 2.0.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass ================================================ FILE: AKImageCropperView/PrimaryFilledButton.swift ================================================ // // PrimaryFilledButton.swift // Visitor // // Created by Artem Krachulov on 1/16/17. // Copyright © 2017 VZPass. All rights reserved. // import Foundation ================================================ FILE: AKImageCropperView.podspec ================================================ Pod::Spec.new do |s| s.name = "AKImageCropperView" s.version = "2.0.0" s.homepage = "https://github.com/artemkrachulov/AKImageCropperView" s.summary = "Responsive image cropper" s.description = <<-DESC Image cropping plugin which supported different devices orientation. Easy to set up and configure. Has many settings for flexible integration into your project. Behavior is similar to native iOS photo cropper. DESC s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "Artem Krachulov" => "artem.krachulov@gmail.com" } # Source Info s.ios.deployment_target = "8.0" s.source = { :git => "https://github.com/artemkrachulov/AKImageCropperView.git", :tag => 'v'+s.version.to_s } s.source_files = "AKImageCropperView/*.{swift}" s.pod_target_xcconfig = { 'SWIFT_VERSION' => '3.0' } end ================================================ FILE: AKImageCropperView.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 011028AB1DFFDD24002AA94E /* AKImageCropperView.h in Headers */ = {isa = PBXBuildFile; fileRef = 011028A91DFFDD24002AA94E /* AKImageCropperView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0141AF1D1E052BF6007C5672 /* AKImageCropperScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0141AF1C1E052BF6007C5672 /* AKImageCropperScrollView.swift */; }; 0155304A1E28F2C500961922 /* IC_UIImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 015530491E28F2C500961922 /* IC_UIImageExtensions.swift */; }; 0155304C1E28FDB800961922 /* IC_CGSizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0155304B1E28FDB800961922 /* IC_CGSizeExtensions.swift */; }; 0155304E1E28FEC600961922 /* IC_CGPointExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0155304D1E28FEC600961922 /* IC_CGPointExtension.swift */; }; 0166B68F1E03E1560081B751 /* AKImageCropperOverlayViewConfigurationOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0166B68E1E03E1560081B751 /* AKImageCropperOverlayViewConfigurationOverlay.swift */; }; 016E5EF21DFFDD8300662D31 /* AKImageCropperOverlayViewTouchState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016E5EE71DFFDD8300662D31 /* AKImageCropperOverlayViewTouchState.swift */; }; 016E5EF31DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016E5EE81DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationCorner.swift */; }; 016E5EF41DFFDD8300662D31 /* AKImageCropperOverlayViewConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016E5EE91DFFDD8300662D31 /* AKImageCropperOverlayViewConfiguration.swift */; }; 016E5EF61DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationEdge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016E5EEB1DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationEdge.swift */; }; 016E5EF71DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016E5EEC1DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationGrid.swift */; }; 016E5EF81DFFDD8300662D31 /* AKImageCropperOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016E5EED1DFFDD8300662D31 /* AKImageCropperOverlayView.swift */; }; 016E5EFB1DFFDD8300662D31 /* AKImageCropperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016E5EF01DFFDD8300662D31 /* AKImageCropperView.swift */; }; 016E5EFE1DFFE77400662D31 /* AKImageCropperView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 011028A71DFFDD24002AA94E /* AKImageCropperView.framework */; }; 016E5EFF1DFFE77400662D31 /* AKImageCropperView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 011028A71DFFDD24002AA94E /* AKImageCropperView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 01C48FBC1DEC250100FBBE34 /* CustomImageCropperOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C48FBB1DEC250100FBBE34 /* CustomImageCropperOverlayView.swift */; }; 01F61BE51E4DC7B800977E33 /* IC_CGFloatExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F61BE41E4DC7B800977E33 /* IC_CGFloatExtension.swift */; }; 01FA13EF1DDF0C6E006B8C3A /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FA13EE1DDF0C6E006B8C3A /* Constants.swift */; }; 4356D6141B8F3DE90033FDBD /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4356D6131B8F3DE90033FDBD /* ImageViewController.swift */; }; 43CBB4031B870EBC00C8F9AE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CBB4021B870EBC00C8F9AE /* AppDelegate.swift */; }; 43CBB4051B870EBC00C8F9AE /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CBB4041B870EBC00C8F9AE /* HomeViewController.swift */; }; 43CBB4081B870EBC00C8F9AE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43CBB4061B870EBC00C8F9AE /* Main.storyboard */; }; 43CBB40A1B870EBC00C8F9AE /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43CBB4091B870EBC00C8F9AE /* Images.xcassets */; }; 43CBB40D1B870EBC00C8F9AE /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 43CBB40B1B870EBC00C8F9AE /* LaunchScreen.xib */; }; 43CBB4241B8716EA00C8F9AE /* CropperViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CBB4231B8716EA00C8F9AE /* CropperViewController.swift */; }; 43CBB4401B8739B100C8F9AE /* ImagesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CBB43F1B8739B100C8F9AE /* ImagesTableViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 016E5F001DFFE77400662D31 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 43CBB3F51B870EBC00C8F9AE /* Project object */; proxyType = 1; remoteGlobalIDString = 011028A61DFFDD24002AA94E; remoteInfo = AKImageCropperView; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 016E5F021DFFE77400662D31 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 016E5EFF1DFFE77400662D31 /* AKImageCropperView.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 011028A71DFFDD24002AA94E /* AKImageCropperView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AKImageCropperView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 011028A91DFFDD24002AA94E /* AKImageCropperView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AKImageCropperView.h; sourceTree = ""; }; 011028AA1DFFDD24002AA94E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0141AF1C1E052BF6007C5672 /* AKImageCropperScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKImageCropperScrollView.swift; sourceTree = ""; }; 015530491E28F2C500961922 /* IC_UIImageExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IC_UIImageExtensions.swift; sourceTree = ""; }; 0155304B1E28FDB800961922 /* IC_CGSizeExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IC_CGSizeExtensions.swift; sourceTree = ""; }; 0155304D1E28FEC600961922 /* IC_CGPointExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IC_CGPointExtension.swift; sourceTree = ""; }; 0166B68E1E03E1560081B751 /* AKImageCropperOverlayViewConfigurationOverlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKImageCropperOverlayViewConfigurationOverlay.swift; sourceTree = ""; }; 016E5EE71DFFDD8300662D31 /* AKImageCropperOverlayViewTouchState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKImageCropperOverlayViewTouchState.swift; sourceTree = ""; }; 016E5EE81DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationCorner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKImageCropperOverlayViewConfigurationCorner.swift; sourceTree = ""; }; 016E5EE91DFFDD8300662D31 /* AKImageCropperOverlayViewConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKImageCropperOverlayViewConfiguration.swift; sourceTree = ""; }; 016E5EEB1DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationEdge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKImageCropperOverlayViewConfigurationEdge.swift; sourceTree = ""; }; 016E5EEC1DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationGrid.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKImageCropperOverlayViewConfigurationGrid.swift; sourceTree = ""; }; 016E5EED1DFFDD8300662D31 /* AKImageCropperOverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKImageCropperOverlayView.swift; sourceTree = ""; }; 016E5EF01DFFDD8300662D31 /* AKImageCropperView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKImageCropperView.swift; sourceTree = ""; }; 01C48FBB1DEC250100FBBE34 /* CustomImageCropperOverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomImageCropperOverlayView.swift; sourceTree = ""; }; 01F61BE41E4DC7B800977E33 /* IC_CGFloatExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IC_CGFloatExtension.swift; sourceTree = ""; }; 01FA13EE1DDF0C6E006B8C3A /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 4356D6131B8F3DE90033FDBD /* ImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = ""; }; 43CBB3FD1B870EBC00C8F9AE /* AKImageCropperViewExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AKImageCropperViewExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43CBB4011B870EBC00C8F9AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43CBB4021B870EBC00C8F9AE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 43CBB4041B870EBC00C8F9AE /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 43CBB4071B870EBC00C8F9AE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 43CBB4091B870EBC00C8F9AE /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 43CBB40C1B870EBC00C8F9AE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 43CBB4231B8716EA00C8F9AE /* CropperViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CropperViewController.swift; sourceTree = ""; }; 43CBB43F1B8739B100C8F9AE /* ImagesTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagesTableViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 011028A31DFFDD24002AA94E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 43CBB3FA1B870EBC00C8F9AE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 016E5EFE1DFFE77400662D31 /* AKImageCropperView.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 011028A81DFFDD24002AA94E /* AKImageCropperView */ = { isa = PBXGroup; children = ( 011028A91DFFDD24002AA94E /* AKImageCropperView.h */, 011028AA1DFFDD24002AA94E /* Info.plist */, 016E5EF01DFFDD8300662D31 /* AKImageCropperView.swift */, 0141AF1C1E052BF6007C5672 /* AKImageCropperScrollView.swift */, 016E5EED1DFFDD8300662D31 /* AKImageCropperOverlayView.swift */, 016E5EE71DFFDD8300662D31 /* AKImageCropperOverlayViewTouchState.swift */, 01F61BE61E4DC89300977E33 /* configuration */, 015530461E28EF5A00961922 /* extensions */, ); path = AKImageCropperView; sourceTree = ""; }; 015530461E28EF5A00961922 /* extensions */ = { isa = PBXGroup; children = ( 015530491E28F2C500961922 /* IC_UIImageExtensions.swift */, 0155304B1E28FDB800961922 /* IC_CGSizeExtensions.swift */, 0155304D1E28FEC600961922 /* IC_CGPointExtension.swift */, 01F61BE41E4DC7B800977E33 /* IC_CGFloatExtension.swift */, ); name = extensions; sourceTree = ""; }; 01F61BE61E4DC89300977E33 /* configuration */ = { isa = PBXGroup; children = ( 016E5EE91DFFDD8300662D31 /* AKImageCropperOverlayViewConfiguration.swift */, 0166B68E1E03E1560081B751 /* AKImageCropperOverlayViewConfigurationOverlay.swift */, 016E5EEB1DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationEdge.swift */, 016E5EE81DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationCorner.swift */, 016E5EEC1DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationGrid.swift */, ); name = configuration; sourceTree = ""; }; 43CBB3F41B870EBC00C8F9AE = { isa = PBXGroup; children = ( 011028A81DFFDD24002AA94E /* AKImageCropperView */, 43CBB3FF1B870EBC00C8F9AE /* AKImageCropperViewExample */, 43CBB3FE1B870EBC00C8F9AE /* Products */, ); sourceTree = ""; }; 43CBB3FE1B870EBC00C8F9AE /* Products */ = { isa = PBXGroup; children = ( 43CBB3FD1B870EBC00C8F9AE /* AKImageCropperViewExample.app */, 011028A71DFFDD24002AA94E /* AKImageCropperView.framework */, ); name = Products; sourceTree = ""; }; 43CBB3FF1B870EBC00C8F9AE /* AKImageCropperViewExample */ = { isa = PBXGroup; children = ( 43CBB4011B870EBC00C8F9AE /* Info.plist */, 43CBB4021B870EBC00C8F9AE /* AppDelegate.swift */, 01FA13EE1DDF0C6E006B8C3A /* Constants.swift */, 43CBB4091B870EBC00C8F9AE /* Images.xcassets */, 43CBB40B1B870EBC00C8F9AE /* LaunchScreen.xib */, 43CBB4061B870EBC00C8F9AE /* Main.storyboard */, 43CBB4041B870EBC00C8F9AE /* HomeViewController.swift */, 43CBB43F1B8739B100C8F9AE /* ImagesTableViewController.swift */, 43CBB4231B8716EA00C8F9AE /* CropperViewController.swift */, 4356D6131B8F3DE90033FDBD /* ImageViewController.swift */, 01C48FBB1DEC250100FBBE34 /* CustomImageCropperOverlayView.swift */, ); path = AKImageCropperViewExample; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 011028A41DFFDD24002AA94E /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 011028AB1DFFDD24002AA94E /* AKImageCropperView.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 011028A61DFFDD24002AA94E /* AKImageCropperView */ = { isa = PBXNativeTarget; buildConfigurationList = 011028B01DFFDD24002AA94E /* Build configuration list for PBXNativeTarget "AKImageCropperView" */; buildPhases = ( 011028A21DFFDD24002AA94E /* Sources */, 011028A31DFFDD24002AA94E /* Frameworks */, 011028A41DFFDD24002AA94E /* Headers */, 011028A51DFFDD24002AA94E /* Resources */, ); buildRules = ( ); dependencies = ( ); name = AKImageCropperView; productName = AKImageCropperView; productReference = 011028A71DFFDD24002AA94E /* AKImageCropperView.framework */; productType = "com.apple.product-type.framework"; }; 43CBB3FC1B870EBC00C8F9AE /* AKImageCropperViewExample */ = { isa = PBXNativeTarget; buildConfigurationList = 43CBB41C1B870EBC00C8F9AE /* Build configuration list for PBXNativeTarget "AKImageCropperViewExample" */; buildPhases = ( 43CBB3F91B870EBC00C8F9AE /* Sources */, 43CBB3FA1B870EBC00C8F9AE /* Frameworks */, 43CBB3FB1B870EBC00C8F9AE /* Resources */, 016E5F021DFFE77400662D31 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( 016E5F011DFFE77400662D31 /* PBXTargetDependency */, ); name = AKImageCropperViewExample; productName = AKImageCropperDemo; productReference = 43CBB3FD1B870EBC00C8F9AE /* AKImageCropperViewExample.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 43CBB3F51B870EBC00C8F9AE /* Project object */ = { isa = PBXProject; attributes = { LastSwiftMigration = 0700; LastSwiftUpdateCheck = 0700; LastUpgradeCheck = 0820; ORGANIZATIONNAME = "Artem Krachulov"; TargetAttributes = { 011028A61DFFDD24002AA94E = { CreatedOnToolsVersion = 8.1; LastSwiftMigration = 0810; ProvisioningStyle = Manual; }; 43CBB3FC1B870EBC00C8F9AE = { CreatedOnToolsVersion = 6.4; DevelopmentTeam = 5JV4U8LEZ8; LastSwiftMigration = 0810; }; }; }; buildConfigurationList = 43CBB3F81B870EBC00C8F9AE /* Build configuration list for PBXProject "AKImageCropperView" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 43CBB3F41B870EBC00C8F9AE; productRefGroup = 43CBB3FE1B870EBC00C8F9AE /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 011028A61DFFDD24002AA94E /* AKImageCropperView */, 43CBB3FC1B870EBC00C8F9AE /* AKImageCropperViewExample */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 011028A51DFFDD24002AA94E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 43CBB3FB1B870EBC00C8F9AE /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 43CBB4081B870EBC00C8F9AE /* Main.storyboard in Resources */, 43CBB40D1B870EBC00C8F9AE /* LaunchScreen.xib in Resources */, 43CBB40A1B870EBC00C8F9AE /* Images.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 011028A21DFFDD24002AA94E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 016E5EF41DFFDD8300662D31 /* AKImageCropperOverlayViewConfiguration.swift in Sources */, 0166B68F1E03E1560081B751 /* AKImageCropperOverlayViewConfigurationOverlay.swift in Sources */, 0155304C1E28FDB800961922 /* IC_CGSizeExtensions.swift in Sources */, 016E5EF81DFFDD8300662D31 /* AKImageCropperOverlayView.swift in Sources */, 0155304A1E28F2C500961922 /* IC_UIImageExtensions.swift in Sources */, 016E5EFB1DFFDD8300662D31 /* AKImageCropperView.swift in Sources */, 016E5EF31DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationCorner.swift in Sources */, 01F61BE51E4DC7B800977E33 /* IC_CGFloatExtension.swift in Sources */, 0155304E1E28FEC600961922 /* IC_CGPointExtension.swift in Sources */, 016E5EF21DFFDD8300662D31 /* AKImageCropperOverlayViewTouchState.swift in Sources */, 0141AF1D1E052BF6007C5672 /* AKImageCropperScrollView.swift in Sources */, 016E5EF71DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationGrid.swift in Sources */, 016E5EF61DFFDD8300662D31 /* AKImageCropperOverlayViewConfigurationEdge.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 43CBB3F91B870EBC00C8F9AE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 43CBB4051B870EBC00C8F9AE /* HomeViewController.swift in Sources */, 01C48FBC1DEC250100FBBE34 /* CustomImageCropperOverlayView.swift in Sources */, 43CBB4241B8716EA00C8F9AE /* CropperViewController.swift in Sources */, 01FA13EF1DDF0C6E006B8C3A /* Constants.swift in Sources */, 43CBB4031B870EBC00C8F9AE /* AppDelegate.swift in Sources */, 4356D6141B8F3DE90033FDBD /* ImageViewController.swift in Sources */, 43CBB4401B8739B100C8F9AE /* ImagesTableViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 016E5F011DFFE77400662D31 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 011028A61DFFDD24002AA94E /* AKImageCropperView */; targetProxy = 016E5F001DFFE77400662D31 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 43CBB4061B870EBC00C8F9AE /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 43CBB4071B870EBC00C8F9AE /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 43CBB40B1B870EBC00C8F9AE /* LaunchScreen.xib */ = { isa = PBXVariantGroup; children = ( 43CBB40C1B870EBC00C8F9AE /* Base */, ); name = LaunchScreen.xib; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 011028B11DFFDD24002AA94E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_MODULES = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AKImageCropperView/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.artemkrachulov.AKImageCropperView; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 011028B21DFFDD24002AA94E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_MODULES = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = AKImageCropperView/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.artemkrachulov.AKImageCropperView; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_VERSION = 3.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 43CBB41A1B870EBC00C8F9AE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 43CBB41B1B870EBC00C8F9AE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 43CBB41D1B870EBC00C8F9AE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = 5JV4U8LEZ8; INFOPLIST_FILE = AKImageCropperViewExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_SWIFT_FLAGS = "-D AKImageCropperViewDEBUG"; PRODUCT_BUNDLE_IDENTIFIER = artem.krachulov.AKImageCropper; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; name = Debug; }; 43CBB41E1B870EBC00C8F9AE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = 5JV4U8LEZ8; INFOPLIST_FILE = AKImageCropperViewExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = No; PRODUCT_BUNDLE_IDENTIFIER = artem.krachulov.AKImageCropper; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 3.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 011028B01DFFDD24002AA94E /* Build configuration list for PBXNativeTarget "AKImageCropperView" */ = { isa = XCConfigurationList; buildConfigurations = ( 011028B11DFFDD24002AA94E /* Debug */, 011028B21DFFDD24002AA94E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 43CBB3F81B870EBC00C8F9AE /* Build configuration list for PBXProject "AKImageCropperView" */ = { isa = XCConfigurationList; buildConfigurations = ( 43CBB41A1B870EBC00C8F9AE /* Debug */, 43CBB41B1B870EBC00C8F9AE /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 43CBB41C1B870EBC00C8F9AE /* Build configuration list for PBXNativeTarget "AKImageCropperViewExample" */ = { isa = XCConfigurationList; buildConfigurations = ( 43CBB41D1B870EBC00C8F9AE /* Debug */, 43CBB41E1B870EBC00C8F9AE /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 43CBB3F51B870EBC00C8F9AE /* Project object */; } ================================================ FILE: AKImageCropperView.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: AKImageCropperView.xcodeproj/project.xcworkspace/xcshareddata/AKImageCropperDemo.xcscmblueprint ================================================ { "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "74FC6B16832EDE035AC047E84A01E2F0C9097FE9", "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { }, "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { "74FC6B16832EDE035AC047E84A01E2F0C9097FE9" : 0, "F0D98E582FA112BC083159DA5B51E17128CD38B4" : 0, "B234E04AE2BC64FD278418F0B83A3F07C2F904DB" : 0, "78558585540D6A03C4B47C1A04640D7B2BD34C66" : 0, "EAD4BE7C3980137405ECAE8A71597A264DEE9FD1" : 0 }, "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "CACF0744-A1D3-4BA8-8481-EB16E6A7A6C0", "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { "74FC6B16832EDE035AC047E84A01E2F0C9097FE9" : "AKImageCropper\/", "F0D98E582FA112BC083159DA5B51E17128CD38B4" : "AKImageCropper\/Demo\/Vendor\/Extensions\/Double-Float\/", "B234E04AE2BC64FD278418F0B83A3F07C2F904DB" : "AKImageCropper\/Demo\/Vendor\/Extensions\/CGRect-CGSize\/", "78558585540D6A03C4B47C1A04640D7B2BD34C66" : "AKImageCropper\/Demo\/Vendor\/Extensions\/Range\/", "EAD4BE7C3980137405ECAE8A71597A264DEE9FD1" : "AKImageCropper\/Demo\/Vendor\/Extensions\/UIImage\/" }, "DVTSourceControlWorkspaceBlueprintNameKey" : "AKImageCropperDemo", "DVTSourceControlWorkspaceBlueprintVersion" : 204, "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Demo\/AKImageCropperDemo.xcodeproj", "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:artemkrachulov\/AKImageCropper.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "74FC6B16832EDE035AC047E84A01E2F0C9097FE9" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/artemkrachulov\/Range-Extension.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "78558585540D6A03C4B47C1A04640D7B2BD34C66" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/artemkrachulov\/CGRect-CGSize-Extensions.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "B234E04AE2BC64FD278418F0B83A3F07C2F904DB" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/artemkrachulov\/UIImage-Extension.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "EAD4BE7C3980137405ECAE8A71597A264DEE9FD1" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/artemkrachulov\/Double-Float-Extensions.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "F0D98E582FA112BC083159DA5B51E17128CD38B4" } ] } ================================================ FILE: AKImageCropperView.xcodeproj/project.xcworkspace/xcshareddata/Demo.xcscmblueprint ================================================ { "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "74FC6B16832EDE035AC047E84A01E2F0C9097FE9", "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { }, "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { "74FC6B16832EDE035AC047E84A01E2F0C9097FE9" : 0, "F0D98E582FA112BC083159DA5B51E17128CD38B4" : 0, "B234E04AE2BC64FD278418F0B83A3F07C2F904DB" : 0, "78558585540D6A03C4B47C1A04640D7B2BD34C66" : 0, "EAD4BE7C3980137405ECAE8A71597A264DEE9FD1" : 0 }, "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "349E57B7-4D5D-430B-958A-5768311D2060", "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { "74FC6B16832EDE035AC047E84A01E2F0C9097FE9" : "AKImageCropper\/", "F0D98E582FA112BC083159DA5B51E17128CD38B4" : "AKImageCropper\/Demo\/Vendor\/Extensions\/Double-Float\/", "B234E04AE2BC64FD278418F0B83A3F07C2F904DB" : "AKImageCropper\/Demo\/Vendor\/Extensions\/CGRect-CGSize\/", "78558585540D6A03C4B47C1A04640D7B2BD34C66" : "AKImageCropper\/Demo\/Vendor\/Extensions\/Range\/", "EAD4BE7C3980137405ECAE8A71597A264DEE9FD1" : "AKImageCropper\/Demo\/Vendor\/Extensions\/UIImage\/" }, "DVTSourceControlWorkspaceBlueprintNameKey" : "Demo", "DVTSourceControlWorkspaceBlueprintVersion" : 204, "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Demo\/Demo.xcodeproj", "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:artemkrachulov\/AKImageCropper.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "74FC6B16832EDE035AC047E84A01E2F0C9097FE9" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/artemkrachulov\/Range-Extension.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "78558585540D6A03C4B47C1A04640D7B2BD34C66" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/artemkrachulov\/CGRect-CGSize-Extensions.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "B234E04AE2BC64FD278418F0B83A3F07C2F904DB" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/artemkrachulov\/UIImage-Extension.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "EAD4BE7C3980137405ECAE8A71597A264DEE9FD1" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/artemkrachulov\/Double-Float-Extensions.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "F0D98E582FA112BC083159DA5B51E17128CD38B4" } ] } ================================================ FILE: AKImageCropperView.xcodeproj/xcshareddata/xcschemes/AKImageCropperView.xcscheme ================================================ ================================================ FILE: AKImageCropperViewExample/AppDelegate.swift ================================================ // // AppDelegate.swift // AKImageCropperDemo // GitHub: https://github.com/artemkrachulov/AKImageCropper // // Created by Krachulov Artem // Copyright (c) 2015 Krachulov Artem. All rights reserved. // Website: http://www.artemkrachulov.com/ // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } ================================================ FILE: AKImageCropperViewExample/Base.lproj/LaunchScreen.xib ================================================ ================================================ FILE: AKImageCropperViewExample/Base.lproj/Main.storyboard ================================================ ================================================ FILE: AKImageCropperViewExample/Constants.swift ================================================ // // Constants.swift // Demo // // Created by Artem Krachulov on 11/18/16. // Copyright © 2016 Artem Krachulov. All rights reserved. // import Foundation struct Constants { static let images = [ ["Attractive-girl", "Autumn-background", "Colorful-pillows"], ["Cupcakes", "Funnel-cake-stand", "Image-of-earth"] ] } ================================================ FILE: AKImageCropperViewExample/CropperViewController.swift ================================================ // // CropperViewController.swift // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // Website: http://www.artemkrachulov.com/ // import UIKit import AKImageCropperView final class CropperViewController: UIViewController { // MARK: - Properties var image: UIImage! // MARK: - Connections: // MARK: -- Outlets private var cropView: AKImageCropperView { return cropViewProgrammatically ?? cropViewStoryboard } @IBOutlet weak var cropViewStoryboard: AKImageCropperView! private var cropViewProgrammatically: AKImageCropperView! @IBOutlet weak var overlayActionView: UIView! @IBOutlet weak var navigationView: UIView! // MARK: -- Actions @IBAction func backAction(_ sender: AnyObject) { guard !cropView.isEdited else { let alertController = UIAlertController(title: "Warning!", message: "All changes will be lost.", preferredStyle: UIAlertControllerStyle.alert) alertController.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.cancel, handler: { _ in _ = self.navigationController?.popViewController(animated: true) })) alertController.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default, handler: nil)) present(alertController, animated: true, completion: nil) return } _ = navigationController?.popViewController(animated: true) } @IBAction func cropRandomAction(_ sender: AnyObject) { // cropView.setCropRectAnin(CGRect(x: 50, y: 200, width: 100, height: 100)) /* let randomWidth = max(UInt32(cropView.configuration.cropRect.minimumSize.width), arc4random_uniform(UInt32(cropView.scrollView.frame.size.width))) let randomHeight = max(UInt32(cropView.configuration.cropRect.minimumSize.height), arc4random_uniform(UInt32(cropView.scrollView.frame.size.height))) let offsetX = CGFloat(arc4random_uniform(UInt32(cropView.scrollView.frame.size.width) - randomWidth)) let offsetY = CGFloat(arc4random_uniform(UInt32(cropView.scrollView.frame.size.height) - randomHeight)) cropView.cropRect(CGRectMake(offsetX, offsetY, CGFloat(randomWidth), CGFloat(randomHeight)))*/ } @IBAction func randomImageAction(_ sender: AnyObject) { let images = Constants.images.flatMap { $0 } cropView.image = UIImage(named: images[Int(arc4random_uniform(UInt32(images.count)))]) angle = 0.0 } @IBAction func cropImageAction(_ sender: AnyObject) { guard let image = cropView.croppedImage else { return } let imageViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ImageViewController") as! ImageViewController imageViewController.image = image navigationController?.pushViewController(imageViewController, animated: true) } @IBAction func showHideOverlayAction(_ sender: AnyObject) { if cropView.isoverlayViewActive { cropView.hideOverlayView(animationDuration: 0.3) UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions.curveLinear, animations: { self.overlayActionView.alpha = 0 }, completion: nil) } else { cropView.showOverlayView(animationDuration: 0.3) UIView.animate(withDuration: 0.3, delay: 0.3, options: UIViewAnimationOptions.curveLinear, animations: { self.overlayActionView.alpha = 1 }, completion: nil) } } var angle: Double = 0.0 @IBAction func rotateAction(_ sender: AnyObject) { angle += M_PI_2 cropView.rotate(angle, withDuration: 0.3, completion: { _ in if self.angle == 2 * M_PI { self.angle = 0.0 } }) } @IBAction func resetAction(_ sender: AnyObject) { cropView.reset(animationDuration: 0.3) angle = 0.0 } // MARK: - Life Cycle override func viewDidLoad() { super.viewDidLoad() navigationController?.isNavigationBarHidden = true // Programmatically initialization /* cropViewProgrammatically = AKImageCropperView() */ // iPhone 4.7" /* cropViewProgrammatically = AKImageCropperView(frame: CGRect(x: 0, y: 20.0, width: 375.0, height: 607.0)) view.addSubview(cropViewProgrammatically) */ // with constraints /* cropViewProgrammatically = AKImageCropperView() cropViewProgrammatically.translatesAutoresizingMaskIntoConstraints = false view.addSubview(cropViewProgrammatically) if #available(iOS 9.0, *) { cropViewProgrammatically.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true cropViewProgrammatically.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true topLayoutGuide.bottomAnchor.constraint(equalTo: cropViewProgrammatically.topAnchor).isActive = true cropViewProgrammatically.bottomAnchor.constraint(equalTo: navigationView.topAnchor).isActive = true } else { for attribute: NSLayoutAttribute in [.top, .left, .bottom, .right] { var toItem: Any? var toAttribute: NSLayoutAttribute! if attribute == .top { toItem = topLayoutGuide toAttribute = .bottom } else if attribute == .bottom { toItem = navigationView toAttribute = .top } else { toItem = view toAttribute = attribute } view.addConstraint( NSLayoutConstraint( item: cropViewProgrammatically, attribute: attribute, relatedBy: NSLayoutRelation.equal, toItem: toItem, attribute: toAttribute, multiplier: 1.0, constant: 0)) } } */ // Inset for overlay action view /* cropView.overlayView?.configuraiton.cropRectInsets.bottom = 50 */ // Custom overlay view configuration /* var customConfiguraiton = AKImageCropperCropViewConfiguration() customConfiguraiton.cropRectInsets.bottom = 50 cropView.overlayView = CustomImageCropperOverlayView(configuraiton: customConfiguraiton) */ cropView.delegate = self cropView.image = image } } // MARK: - AKImageCropperViewDelegate extension CropperViewController: AKImageCropperViewDelegate { func imageCropperViewDidChangeCropRect(view: AKImageCropperView, cropRect rect: CGRect) { // print("New crop rectangle: \(rect)") } } ================================================ FILE: AKImageCropperViewExample/CustomImageCropperOverlayView.swift ================================================ // // CustomImageCropperOverlayView.swift // Demo // // Created by Artem Krachulov on 11/28/16. // Copyright © 2016 Artem Krachulov. All rights reserved. // import Foundation import AKImageCropperView final class CustomImageCropperOverlayView: AKImageCropperOverlayView { private func drawCycleInCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { var color: UIColor var width: CGFloat if state == .normal { color = configuraiton.corner.normalLineColor width = configuraiton.corner.normalLineWidth } else { color = configuraiton.corner.highlightedLineColor width = configuraiton.corner.highlightedLineWidth } let layer: CAShapeLayer = view.layer.sublayers!.first as! CAShapeLayer let circlePath = UIBezierPath( arcCenter: CGPoint(x: touchView.bounds.midX, y: touchView.bounds.midY), radius: width, startAngle: 0.0, endAngle: CGFloat(M_PI * 2), clockwise: true) layer.path = circlePath.cgPath layer.fillColor = color.cgColor layer.strokeColor = color.cgColor } override func layoutTopLeftCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { drawCycleInCornerView(view, inTouchView: touchView, forState: state) } override func layoutTopRightCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { drawCycleInCornerView(view, inTouchView: touchView, forState: state) } override func layoutBottomLeftCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { drawCycleInCornerView(view, inTouchView: touchView, forState: state) } override func layoutBottomRightCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKImageCropperCropViewTouchState) { drawCycleInCornerView(view, inTouchView: touchView, forState: state) } } ================================================ FILE: AKImageCropperViewExample/HomeViewController.swift ================================================ // // HomeViewController.swift // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // Website: http://www.artemkrachulov.com/ // import UIKit final class HomeViewController: UIViewController { // MARK: - Connections: // MARK: -- Actions @IBAction func galleryAction() { if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.savedPhotosAlbum) { let imagePicker = UIImagePickerController() imagePicker.delegate = self imagePicker.sourceType = UIImagePickerControllerSourceType.savedPhotosAlbum imagePicker.allowsEditing = false present(imagePicker, animated: true, completion: nil) } } // MARK: - Life Cycle override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = false } } // MARK: - UIImagePickerControllerDelegate extension HomeViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage { let cropperViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "cropperViewController") as! CropperViewController cropperViewController.image = pickedImage picker.pushViewController(cropperViewController, animated: true) } } } ================================================ FILE: AKImageCropperViewExample/ImageViewController.swift ================================================ // // ImageViewController.swift // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // Website: http://www.artemkrachulov.com/ // import UIKit final class ImageViewController: UIViewController { // MARK: - Properties var image: UIImage! // MARK: - Connections: // MARK: -- Outlets @IBOutlet weak var imageView: UIImageView! // MARK: -- Actions @IBAction func backAction(_ sender: UIButton) { _ = navigationController?.popViewController(animated: true) } @IBAction func showListAction(_ sender: UIButton) { if presentingViewController != nil { _ = navigationController?.popToRootViewController(animated: true) } else { _ = navigationController?.popToViewController(navigationController!.viewControllers[1], animated: true) } } // MARK: - Life Cycle override func viewDidLoad() { super.viewDidLoad() imageView.image = image } } ================================================ FILE: AKImageCropperViewExample/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: AKImageCropperViewExample/Images.xcassets/Attractive-girl.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "Attractive-girl-on-a-yacht-at-summer-day.jpg" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: AKImageCropperViewExample/Images.xcassets/Autumn-background.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "Autumn-background.jpg" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: AKImageCropperViewExample/Images.xcassets/Colorful-pillows.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "Colorful-pillows-on-a-sofa-with-white-brick-wall-in-background.jpg" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: AKImageCropperViewExample/Images.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: AKImageCropperViewExample/Images.xcassets/Cupcakes.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "Cupcakes.jpg" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: AKImageCropperViewExample/Images.xcassets/Funnel-cake-stand.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "Funnel-cake-stand-at-a-fair.jpg" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: AKImageCropperViewExample/Images.xcassets/Icons/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: AKImageCropperViewExample/Images.xcassets/Icons/overlay.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "overlay.pdf" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: AKImageCropperViewExample/Images.xcassets/Icons/random.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "random.pdf" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: AKImageCropperViewExample/Images.xcassets/Icons/rotate.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "rotate.pdf" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "template" } } ================================================ FILE: AKImageCropperViewExample/Images.xcassets/Image-of-earth.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "Image-of-earth-planet.-Elements-of-this-image-are-furnished-by-NASA.jpg" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: AKImageCropperViewExample/ImagesTableViewController.swift ================================================ // // ImagesTableViewController.swift // // Created by Artem Krachulov. // Copyright (c) 2016 Artem Krachulov. All rights reserved. // Website: http://www.artemkrachulov.com/ // import UIKit final class ImagesTableViewController: UITableViewController { // MARK: - Life Cycle override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = false } // MARK: - Navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { let selectedPath = tableView.indexPathForSelectedRow! (segue.destination as! CropperViewController).image = UIImage(named: Constants.images[selectedPath.section][selectedPath.row]) } } // MARK: - Table view data source extension ImagesTableViewController { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return section == 0 ? "Large" : "Small" } override func numberOfSections(in tableView: UITableView) -> Int { return Constants.images.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return Constants.images[section].count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "image", for: indexPath) let name = Constants.images[indexPath.section][indexPath.row] let image = UIImage(named: name) // Configure the cell... cell.textLabel!.text = name.components(separatedBy: "-").joined(separator: " ") cell.detailTextLabel?.text = String(format: "Size %0.1f x %0.1f", image?.size.width as CGFloat!, image?.size.height as CGFloat!) return cell } } ================================================ FILE: AKImageCropperViewExample/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS NSCameraUsageDescription $(PRODUCT_NAME) camera use NSPhotoLibraryUsageDescription $(PRODUCT_NAME) photo use UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: LICENSE ================================================ Copyright (c) 2015-2016 Artem Krachulov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # AKImageCropper > Responsive image cropper [![Carthage compatible][carthage-bage]][carthage-bage] [![CocoaPods Compatible][pods-bage]][pods-bage] [![Platform][platform-bage]][platform-bage] [![Swift Version][swift-bage]][swift-url] [![Build Status][travis-bage]][travis-url] [![License][license-bage]][license-url] [pods-bage]: https://img.shields.io/badge/COCOAPODS-compatible-fb0006.svg [pods-url]: https://cocoapods.org/ [carthage-bage]: https://img.shields.io/badge/Carthage-compatible-brightgreen.svg [carthage-url]: https://github.com/Carthage/Carthage [platform-bage]: https://img.shields.io/cocoapods/p/LFAlertController.svg [platform-url]: http://cocoapods.org/pods/LFAlertController [swift-bage]:https://img.shields.io/badge/swift-3.0-orange.svg [swift-url]: https://swift.org/ [license-bage]: https://img.shields.io/badge/License-MIT-blue.svg [license-url]: LICENSE [travis-bage]: https://img.shields.io/travis/dbader/node-datadog-metrics/master.svg [travis-url]: https://travis-ci.org/dbader/node-datadog-metrics Image cropping plugin which supported different devices orientation. Easy to set up and configure. Has many settings for flexible integration into your project. Behavior is similar to native iOS photo cropper. ![](header.gif) ## Features - [x] Overlay view & Crop rectangle full customization - [x] Flexible settings - [x] Image rotation - [x] Infinite "Zoom To Fit" - [x] Full image resolution - [x] Ability to draw custom crop rectangle ## Requirements - iOS 8.0+ - Xcode 7.3 ## Installation ### CocoaPods [CocoaPods][] is a dependency manager for Cocoa projects. To install **AKImageCropperView** with CocoaPods: 1. Make sure CocoaPods is [installed][CocoaPods Installation]. 2. Update your Podfile to include the following: ``` ruby use_frameworks! pod 'AKImageCropperView' ``` 3. Run `pod install`. [CocoaPods]: https://cocoapods.org [CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started 4. In your code import **AKImageCropperView** like so: `import AKImageCropperView` ### Carthage [Carthage][] is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To install **AKImageCropperView** with Carthage: 1. Install Carthage via [Homebrew][] ```bash $ brew update $ brew install carthage ``` 2. Add `github "artemkrachulov/AKImageCropperView"` to your Cartfile. 3. Run `carthage update`. 4. Drag `AKMaskField.framework ` from the `Carthage/Build/iOS/` directory to the `Linked Frameworks and Libraries` section of your Xcode project’s `General` settings. 5. Add `$(SRCROOT)/Carthage/Build/iOS/AKImageCropperView.framework ` to `Input Files` of Run Script Phase for Carthage. [Carthage]: https://github.com/Carthage/Carthage [Homebrew]: http://brew.sh ### Manual If you prefer not to use either of the aforementioned dependency managers, you can integrate **AKImageCropperView** into your project manually. 1. Download and drop **AKImageCropperView** folder in your project. 2. Done! ## Usage example ### Storyboard ```swift @IBOutlet weak var cropView: AKImageCropperView! override func viewDidLoad() { super.viewDidLoad() cropView.image = UIImage(named: "yourImage") } ``` ### Programmatically ```swift var cropView: AKImageCropperView! override func viewDidLoad() { super.viewDidLoad() cropView = AKImageCropperView(frame: CGRect(x: 0, y: 20.0, width: 375.0, height: 607.0)) cropView.image = UIImage(named: "yourImage") view.addSubview(cropViewProgrammatically) } ``` > Full examples with constraint, delegates and Overlay view configuration check in demo project. > > **NOTE**: If after cropper view initialization your image has top inset. Go to storyboard with your scene and in the attributes inspector, uncheck 'Adjust Scrollview Insets'. ## Initializing an Image Cropper View ```swift func init(image: UIImage?) ``` Returns an image cropper view initialized with the specified image. **Parameters** - `image` : The initial image to display in the image cropper view. ## Accessing the Displayed Images ```swift var image: UIImage? { get set } ``` The image displayed in the image cropper view. Default value of this property is `nil`. ```swift var croppedImage: UIImage? { get } ``` Cropperd image in the specified crop rectangle. ## Instance Properties ```swift var isEdited: UIImage? { get } ``` Returns the image edited state flag. ## Managing the Delegate ```swift weak var delegate: AKImageCropperViewDelegate? { get set } ``` The delegate of the cropper view object. ### Delegate methods ```swift optional func imageCropperViewDidChangeCropRect(view: AKImageCropperView, cropRect rect: CGRect) ``` Tells the delegate that crop frame was changed. Parameters: - **`view`**: The image cropper view. - **`rect`**: New crop rectangle origin and size. ## Customizing an Overlay View ```swift var overlayView: AKImageCropperOverlayView? { get set } ``` Overlay view represented as AKImageCropperOverlayView open class. Base configuration and behavior can be set or changed with **AKImageCropperOverlayConfiguration** structure. For deep visual changes create the children class and make the necessary configuration in the overrided methods. ### Initializing an Overlay View ```swift init(configuraiton: AKImageCropperOverlayConfiguration? = default) ``` Returns an overlay view initialized with the specified configuraiton. ### Base configuration ```swift var configuraiton: AKImageCropperOverlayConfiguration { get set } ``` Configuration structure for the Overlay View appearance and behavior. #### AKImageCropperOverlayConfiguration ```swift var zoomingToFitDelay: TimeInterval { get set } ``` Delay before the crop rectangle will scale to fit cropper view frame. Default value of this property is `2`. ```swift var animation: (duration: TimeInterval, options: UIViewAnimationOptions) { get set } ``` Animation options for layout transitions. Values: - **`duration`** : The duration of the transition animation, measured in seconds. The default value is `300`. - **`options`** : Specifies the supported animation curves. The default value is `.curveEaseInOut`. ```swift var cropRectInsets: UIEdgeInsets { get set } ``` Edges insets for crop rectangle. Static values for programmatically rotation. Default value of this property is `20` px for each edge. ```swift var minCropRectSize: CGSize { get set } ``` The smallest value for the crop rectangle size. Default value of this property is `60` pixels width and `60` pixels height. ```swift var cornerTouchSize: CGSize { get set } ``` Touch view where will be drawn the corner. Default value of this property is `30` pixels width and `30` pixels height. ```swift var edgeThickness: (vertical: CGFloat, horizontal: CGFloat) { get set } ``` Thickness for edges touch area. This touch view is centered on the edge line. - **`vertical`** : Thickness for vertical edges: Left, Right. The default value is `20`. - **`horizontal`** : Thickness for horizontal edges: Top, Bottom. The default value is `.curveEaseInOut`. ```swift var edge: AKImageCropperCropViewConfigurationOverlay { get set } ``` Overlay visual configuration. ```swift var edge: AKImageCropperCropViewConfigurationEdge { get set } ``` Edges visual configuration. Check this struct below. ```swift var corner: AKImageCropperCropViewConfigurationCorner { get set } ``` Corners visual configuration. Check this struct below. ```swift var grid: AKImageCropperCropViewConfigurationGrid { get set } ``` Grid visual configuration. Check this struct below. #### AKImageCropperCropViewConfigurationOverlay ```swift var backgroundColor: UIColor { get set } ``` The view’s background color. Default value is `. black` with alpha `0.5`. ```swift var isBlurEnabled: Bool { get set } ``` A Boolean value that determines whether the blur effect is enable. The blur effect added over overlay view. The effect will disappear before user interaction will start. After manipulations, the effect will revert to the initial state. Default value is `true`. ```swift var blurStyle: Bool { get set } ``` The intensity of the blur effect. Default value is `.dark`. ```swift var blurAlpha: Bool { get set } ``` The blur effect alpha value. Default value is `0.6`. #### AKImageCropperCropViewConfigurationEdge ```swift var isHidden: Bool { get set } ``` A Boolean value that determines whether the edge view is hidden. Default value is `false`. ```swift var normalLineWidth: CGFloat { get set } ``` Line width for normal edge state. Default value is `1.0`. ```swift var highlightedLineWidth: CGFloat { get set } ``` Line width for highlighted edge state. Default value is `3.0`. ```swift var normalLineColor: UIColor { get set } ``` Line color for normal edge state. Default value is `.white`. ```swift var highlightedLineColor: UIColor { get set } ``` Line color for highlighted edge state. Default value is `white`. #### AKImageCropperCropViewConfigurationCorner ```swift var isHidden: Bool { get set } ``` A Boolean value that determines whether the corner view is hidden. Default value is `false`. ```swift var normalLineWidth: CGFloat { get set } ``` Line width for normal corner state. Default value is `3.0`. ```swift var highlightedLineWidth: CGFloat { get set } ``` Line width for highlighted corner state. Default value is `3.0`. ```swift var normaSize: CGSize { get set } ``` Size for normal corner state. Default value is `20` pixels width and `20` pixels height. ```swift var highlightedSize: CGSize { get set } ``` Size for highlighted corner state. Default value is `30` pixels width and `30` pixels height. ```swift var normalLineColor: UIColor { get set } ``` Line color for normal corner state. Default value is `.white`. ```swift var highlightedLineColor: UIColor { get set } ``` Line color for highlighted corner state. Default value is `.white`. #### AKImageCropperCropViewConfigurationGrid ```swift var isHidden: Bool { get set } ``` A Boolean value that determines whether the grid views is hidden. Default value is `false`. ```swift var autoHideGrid: Bool { get set } ``` Hide grid after user interaction. Default value is `true`. ```swift var linesCount: (vertical: Int, horizontal: Int) { get set } ``` The number of vertical and horizontal lines inside the crop rectangle. - **`vertical`** : Vertical lines count. The default value is `2`. - **`horizontal`** : Horizontal lines count. The default value is `2`. ```swift var linesWidth: CGFloat { get set } ``` Vertical and horizontal lines width. Default value is `1.0`. ```swift var linesColor: UIColor { get set } ``` Vertical and horizontal lines color. Default value is `white` with alpha `0.5`. #### Visual Appearance ```swift func layoutTopEdgeView(_ view: UIView, inTouchView touchView: UIView, forState state: AKCropAreaPartState) ``` Visual representation for top edge view in current user interaction state. Parameters: - **`view`**: Top edge view. - **`touchView`**: Touch area view where added top edge view. - **`state`**: User interaction state. ```swift func layoutRightEdgeView(_ view: UIView, inTouchView touchView: UIView, forState state: AKCropAreaPartState) ``` Visual representation for right edge view in current user interaction state. Parameters: - **`view`**: Right edge view. - **`touchView`**: Touch area view where added right edge view. - **`state`**: User interaction state. ```swift func layoutBottomEdgeView(_ view: UIView, inTouchView touchView: UIView, forState state: AKCropAreaPartState) ``` Visual representation for bottom edge view in current user interaction state. Parameters: - **`view`**: Bottom edge view. - **`touchView`**: Touch area view where added bottom edge view. - **`state`**: User interaction state. ```swift func layoutLeftEdgeView(_ view: UIView, inTouchView touchView: UIView, forState state: AKCropAreaPartState) ``` Visual representation for left edge view in current user interaction state. Parameters: - **`view`**: Left edge view. - **`touchView`**: Touch area view where added left edge view. - **`state`**: User interaction state. ```swift func layoutTopLeftCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKCropAreaPartState) ``` Visual representation for top left corner view in current user interaction state. Drawing going with added shape layer. Parameters: - **`view`**: Top left corner view. - **`touchView`**: Touch area view where added top left edge view. - **`state `**: User interaction state. ```swift func layoutTopRightCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKCropAreaPartState) ``` Visual representation for top right corner view in current user interaction state. Drawing going with added shape layer. Parameters: - **`view`**: Top right corner view. - **`touchView`**: Touch area view where added top right edge view. - **`state `**: User interaction state. ```swift func layoutBottomRightCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKCropAreaPartState) ``` Visual representation for bottom right corner view in current user interaction state. Drawing going with added shape layer. Parameters: - **`view`**: Bottom right corner view. - **`touchView`**: Touch area view where added bottom right edge view. - **`state `**: User interaction state. ```swift func layoutBottomLeftCornerView(_ view: UIView, inTouchView touchView: UIView, forState state: AKCropAreaPartState) ``` Visual representation for bottom left corner view in current user interaction state. Drawing going with added shape layer. Parameters: - **`view`**: Bottom left corner view. - **`touchView`**: Touch area view where added bottom left edge view. - **`state `**: User interaction state. ```swift func layoutGridView(_ view: UIView, gridViewHorizontalLines: [UIView], gridViewVerticalLines: [UIView]) ``` Visual representation for grid view. Parameters: - **`view`**: Grid view. - **`gridViewHorizontalLines`**: Horizontal line view`s array. - **`gridViewVerticalLines `**: Vertical line view`s array. ## Contribute Please do not forget to ★ this repository to increases its visibility and encourages others to contribute. Got a bug fix, or a new feature? Create a pull request and go for it! ## Meta Artem Krachulov – [www.artemkrachulov.com](http://www.artemkrachulov.com/) - [artem.krachulov@gmail.com](mailto:artem.krachulov@gmail.com) Released under the [MIT license](http://www.opensource.org/licenses/MIT) [https://github.com/artemkrachulov](https://github.com/dbader/)