123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- //
- // RectangleView.swift
- // WeScan
- //
- // Created by Boris Emorine on 2/8/18.
- // Copyright © 2018 WeTransfer. All rights reserved.
- //
- import UIKit
- import AVFoundation
- /// Simple enum to keep track of the position of the corners of a quadrilateral.
- enum CornerPosition {
- case topLeft
- case topRight
- case bottomRight
- case bottomLeft
- }
- /// The `QuadrilateralView` is a simple `UIView` subclass that can draw a quadrilateral, and optionally edit it.
- final class QuadrilateralView: UIView {
-
- private let quadLayer: CAShapeLayer = {
- let layer = CAShapeLayer()
- layer.strokeColor = UIColor.white.cgColor
- layer.lineWidth = 1.0
- layer.opacity = 1.0
- layer.isHidden = true
-
- return layer
- }()
-
- /// We want the corner views to be displayed under the outline of the quadrilateral.
- /// Because of that, we need the quadrilateral to be drawn on a UIView above them.
- private let quadView: UIView = {
- let view = UIView()
- view.backgroundColor = UIColor.clear
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
-
- /// The quadrilateral drawn on the view.
- private(set) var quad: Quadrilateral?
-
- public var editable = false {
- didSet {
- editable == true ? showCornerViews() : hideCornerViews()
- quadLayer.fillColor = editable ? UIColor(white: 0.0, alpha: 0.6).cgColor : UIColor(white: 1.0, alpha: 0.5).cgColor
- guard let quad = quad else {
- return
- }
- drawQuad(quad, animated: false)
- layoutCornerViews(forQuad: quad)
- }
- }
-
- private var isHighlighted = false {
- didSet (oldValue) {
- guard oldValue != isHighlighted else {
- return
- }
- quadLayer.fillColor = isHighlighted ? UIColor.clear.cgColor : UIColor(white: 0.0, alpha: 0.6).cgColor
- isHighlighted ? bringSubviewToFront(quadView) : sendSubviewToBack(quadView)
- }
- }
-
- lazy private var topLeftCornerView: EditScanCornerView = {
- return EditScanCornerView(frame: CGRect(x: 0.0, y: 0.0, width: cornerViewSize, height: cornerViewSize), position: .topLeft)
- }()
-
- lazy private var topRightCornerView: EditScanCornerView = {
- return EditScanCornerView(frame: CGRect(x: 0.0, y: 0.0, width: cornerViewSize, height: cornerViewSize), position: .topRight)
- }()
-
- lazy private var bottomRightCornerView: EditScanCornerView = {
- return EditScanCornerView(frame: CGRect(x: 0.0, y: 0.0, width: cornerViewSize, height: cornerViewSize), position: .bottomRight)
- }()
-
- lazy private var bottomLeftCornerView: EditScanCornerView = {
- return EditScanCornerView(frame: CGRect(x: 0.0, y: 0.0, width: cornerViewSize, height: cornerViewSize), position: .bottomLeft)
- }()
-
- private let highlightedCornerViewSize: CGFloat = 75.0
- private let cornerViewSize: CGFloat = 20.0
-
- // MARK: - Life Cycle
-
- override init(frame: CGRect) {
- super.init(frame: frame)
- commonInit()
- }
-
- required public init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- private func commonInit() {
- addSubview(quadView)
- setupCornerViews()
- setupConstraints()
- quadView.layer.addSublayer(quadLayer)
- }
-
- private func setupConstraints() {
- let quadViewConstraints = [
- quadView.topAnchor.constraint(equalTo: topAnchor),
- quadView.leadingAnchor.constraint(equalTo: leadingAnchor),
- bottomAnchor.constraint(equalTo: quadView.bottomAnchor),
- trailingAnchor.constraint(equalTo: quadView.trailingAnchor)
- ]
-
- NSLayoutConstraint.activate(quadViewConstraints)
- }
-
- private func setupCornerViews() {
- addSubview(topLeftCornerView)
- addSubview(topRightCornerView)
- addSubview(bottomRightCornerView)
- addSubview(bottomLeftCornerView)
- }
-
- override public func layoutSubviews() {
- super.layoutSubviews()
- guard quadLayer.frame != bounds else {
- return
- }
-
- quadLayer.frame = bounds
- if let quad = quad {
- drawQuadrilateral(quad: quad, animated: false)
- }
- }
-
- // MARK: - Drawings
-
- /// Draws the passed in quadrilateral.
- ///
- /// - Parameters:
- /// - quad: The quadrilateral to draw on the view. It should be in the coordinates of the current `QuadrilateralView` instance.
- func drawQuadrilateral(quad: Quadrilateral, animated: Bool) {
- self.quad = quad
- drawQuad(quad, animated: animated)
- if editable {
- showCornerViews()
- layoutCornerViews(forQuad: quad)
- }
- }
-
- private func drawQuad(_ quad: Quadrilateral, animated: Bool) {
- var path = quad.path()
-
- if editable {
- path = path.reversing()
- let rectPath = UIBezierPath(rect: bounds)
- path.append(rectPath)
- }
-
- if animated == true {
- let pathAnimation = CABasicAnimation(keyPath: "path")
- pathAnimation.duration = 0.2
- quadLayer.add(pathAnimation, forKey: "path")
- }
-
- quadLayer.path = path.cgPath
- quadLayer.isHidden = false
- }
-
- private func layoutCornerViews(forQuad quad: Quadrilateral) {
- topLeftCornerView.center = quad.topLeft
- topRightCornerView.center = quad.topRight
- bottomLeftCornerView.center = quad.bottomLeft
- bottomRightCornerView.center = quad.bottomRight
- }
-
- func removeQuadrilateral() {
- quadLayer.path = nil
- quadLayer.isHidden = true
- }
-
- // MARK: - Actions
-
- func moveCorner(cornerView: EditScanCornerView, atPoint point: CGPoint) {
- guard let quad = quad else {
- return
- }
-
- let validPoint = self.validPoint(point, forCornerViewOfSize: cornerView.bounds.size, inView: self)
-
- cornerView.center = validPoint
- let updatedQuad = update(quad, withPosition: validPoint, forCorner: cornerView.position)
-
- self.quad = updatedQuad
- drawQuad(updatedQuad, animated: false)
- }
-
- func highlightCornerAtPosition(position: CornerPosition, with image: UIImage) {
- guard editable else {
- return
- }
- isHighlighted = true
-
- let cornerView = cornerViewForCornerPosition(position: position)
- guard cornerView.isHighlighted == false else {
- cornerView.highlightWithImage(image)
- return
- }
-
- cornerView.frame = CGRect(x: cornerView.frame.origin.x - (highlightedCornerViewSize - cornerViewSize) / 2.0, y: cornerView.frame.origin.y - (highlightedCornerViewSize - cornerViewSize) / 2.0, width: highlightedCornerViewSize, height: highlightedCornerViewSize)
- cornerView.highlightWithImage(image)
- }
-
- func resetHighlightedCornerViews() {
- isHighlighted = false
- resetHighlightedCornerViews(cornerViews: [topLeftCornerView, topRightCornerView, bottomLeftCornerView, bottomRightCornerView])
- }
-
- private func resetHighlightedCornerViews(cornerViews: [EditScanCornerView]) {
- cornerViews.forEach { (cornerView) in
- resetHightlightedCornerView(cornerView: cornerView)
- }
- }
-
- private func resetHightlightedCornerView(cornerView: EditScanCornerView) {
- cornerView.reset()
- cornerView.frame = CGRect(x: cornerView.frame.origin.x + (cornerView.frame.size.width - cornerViewSize) / 2.0, y: cornerView.frame.origin.y + (cornerView.frame.size.width - cornerViewSize) / 2.0, width: cornerViewSize, height: cornerViewSize)
- cornerView.setNeedsDisplay()
- }
-
- // MARK: Validation
-
- /// Ensures that the given point is valid - meaning that it is within the bounds of the passed in `UIView`.
- ///
- /// - Parameters:
- /// - point: The point that needs to be validated.
- /// - cornerViewSize: The size of the corner view representing the given point.
- /// - view: The view which should include the point.
- /// - Returns: A new point which is within the passed in view.
- private func validPoint(_ point: CGPoint, forCornerViewOfSize cornerViewSize: CGSize, inView view: UIView) -> CGPoint {
- var validPoint = point
-
- if point.x > view.bounds.width {
- validPoint.x = view.bounds.width
- } else if point.x < 0.0 {
- validPoint.x = 0.0
- }
-
- if point.y > view.bounds.height {
- validPoint.y = view.bounds.height
- } else if point.y < 0.0 {
- validPoint.y = 0.0
- }
-
- return validPoint
- }
-
- // MARK: - Convenience
-
- private func hideCornerViews() {
- topLeftCornerView.isHidden = true
- topRightCornerView.isHidden = true
- bottomRightCornerView.isHidden = true
- bottomLeftCornerView.isHidden = true
- }
-
- private func showCornerViews() {
- topLeftCornerView.isHidden = false
- topRightCornerView.isHidden = false
- bottomRightCornerView.isHidden = false
- bottomLeftCornerView.isHidden = false
- }
-
- private func update(_ quad: Quadrilateral, withPosition position: CGPoint, forCorner corner: CornerPosition) -> Quadrilateral {
- var quad = quad
-
- switch corner {
- case .topLeft:
- quad.topLeft = position
- case .topRight:
- quad.topRight = position
- case .bottomRight:
- quad.bottomRight = position
- case .bottomLeft:
- quad.bottomLeft = position
- }
-
- return quad
- }
-
- func cornerViewForCornerPosition(position: CornerPosition) -> EditScanCornerView {
- switch position {
- case .topLeft:
- return topLeftCornerView
- case .topRight:
- return topRightCornerView
- case .bottomLeft:
- return bottomLeftCornerView
- case .bottomRight:
- return bottomRightCornerView
- }
- }
- }
|