CropOverlay.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. //
  2. // CropOverlay.swift
  3. // ALCameraViewController
  4. //
  5. // Created by Alex Littlejohn on 2015/06/30.
  6. // Copyright (c) 2015 zero. All rights reserved.
  7. //
  8. // Modified by Kevin Kieffer on 2019/08/06. Changes as follows:
  9. // Added a center point circle that shows the touch point for moving the crop overlay. Previously, the user could move the
  10. // overlay by dragging anywhere in the bounds, but this prevents pinch-resizing of the image except at the very edge.
  11. // Here, the user must touch within the center point to move. The resize corners still work as before.
  12. import UIKit
  13. internal class CropOverlay: UIView {
  14. private class CenterCircleView : UIView {
  15. override init(frame : CGRect) {
  16. super.init(frame: frame)
  17. contentMode = .redraw
  18. backgroundColor = .clear
  19. isOpaque = false
  20. }
  21. required init?(coder aDecoder: NSCoder) {
  22. fatalError("init(coder:) has not been implemented")
  23. }
  24. override func draw(_ rect: CGRect) {
  25. if let context = UIGraphicsGetCurrentContext() {
  26. let radius = bounds.width/2 - 1.0 //account for stroke width
  27. context.addArc(center: CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
  28. UIColor.blue.setStroke()
  29. context.strokePath()
  30. }
  31. }
  32. }
  33. var outerLines = [UIView]()
  34. var horizontalLines = [UIView]()
  35. var verticalLines = [UIView]()
  36. private var centerCircle = CenterCircleView()
  37. private var centerCircleRadius : CGFloat {
  38. get {
  39. let minSide = min(bounds.width, bounds.height)
  40. return min(minSide, 34.0)
  41. }
  42. }
  43. var topLeftCornerLines = [UIView]()
  44. var topRightCornerLines = [UIView]()
  45. var bottomLeftCornerLines = [UIView]()
  46. var bottomRightCornerLines = [UIView]()
  47. var cornerButtons = [UIButton]()
  48. let cornerLineDepth: CGFloat = 3
  49. let cornerLineWidth: CGFloat = 22.5
  50. var cornerButtonWidth: CGFloat {
  51. return self.cornerLineWidth * 2
  52. }
  53. let lineWidth: CGFloat = 1
  54. let outterGapRatio: CGFloat = 1/3
  55. var outterGap: CGFloat {
  56. return self.cornerButtonWidth * self.outterGapRatio
  57. }
  58. var isResizable: Bool = false
  59. var isMovable: Bool = false
  60. var showsCenterPoint = false
  61. var showsButtons = true
  62. var minimumSize: CGSize = CGSize.zero
  63. internal override init(frame: CGRect) {
  64. super.init(frame: frame)
  65. createLines()
  66. addSubview(centerCircle)
  67. }
  68. internal required init?(coder aDecoder: NSCoder) {
  69. super.init(coder: aDecoder)
  70. createLines()
  71. addSubview(centerCircle)
  72. }
  73. override func layoutSubviews() {
  74. for i in 0..<outerLines.count {
  75. let line = outerLines[i]
  76. var lineFrame: CGRect
  77. switch (i) {
  78. case 0:
  79. lineFrame = CGRect(x: outterGap, y: outterGap, width: bounds.width - outterGap * 2, height: lineWidth)
  80. break
  81. case 1:
  82. lineFrame = CGRect(x: bounds.width - lineWidth - outterGap, y: outterGap, width: lineWidth, height: bounds.height - outterGap * 2)
  83. break
  84. case 2:
  85. lineFrame = CGRect(x: outterGap, y: bounds.height - lineWidth - outterGap, width: bounds.width - outterGap * 2, height: lineWidth)
  86. break
  87. case 3:
  88. lineFrame = CGRect(x: outterGap, y: outterGap, width: lineWidth, height: bounds.height - outterGap * 2)
  89. break
  90. default:
  91. lineFrame = CGRect.zero
  92. break
  93. }
  94. line.frame = lineFrame
  95. }
  96. if showsCenterPoint {
  97. let r = centerCircleRadius
  98. centerCircle.frame = CGRect(x: bounds.midX - r, y: bounds.midY - r, width: r * 2, height: r * 2)
  99. centerCircle.setNeedsDisplay()
  100. }
  101. if showsButtons {
  102. let corners = [topLeftCornerLines, topRightCornerLines, bottomLeftCornerLines, bottomRightCornerLines]
  103. for i in 0..<corners.count {
  104. let corner = corners[i]
  105. var horizontalFrame: CGRect
  106. var verticalFrame: CGRect
  107. var buttonFrame: CGRect
  108. let buttonSize = CGSize(width: cornerButtonWidth, height: cornerButtonWidth)
  109. switch (i) {
  110. case 0: // Top Left
  111. verticalFrame = CGRect(x: outterGap, y: outterGap, width: cornerLineDepth, height: cornerLineWidth)
  112. horizontalFrame = CGRect(x: outterGap, y: outterGap, width: cornerLineWidth, height: cornerLineDepth)
  113. buttonFrame = CGRect(origin: CGPoint(x: 0, y: 0), size: buttonSize)
  114. case 1: // Top Right
  115. verticalFrame = CGRect(x: bounds.width - cornerLineDepth - outterGap, y: outterGap, width: cornerLineDepth, height: cornerLineWidth)
  116. horizontalFrame = CGRect(x: bounds.width - cornerLineWidth - outterGap, y: outterGap, width: cornerLineWidth, height: cornerLineDepth)
  117. buttonFrame = CGRect(origin: CGPoint(x: bounds.width - cornerButtonWidth, y: 0), size: buttonSize)
  118. case 2: // Bottom Left
  119. verticalFrame = CGRect(x: outterGap, y: bounds.height - cornerLineWidth - outterGap, width: cornerLineDepth, height: cornerLineWidth)
  120. horizontalFrame = CGRect(x: outterGap, y: bounds.height - cornerLineDepth - outterGap, width: cornerLineWidth, height: cornerLineDepth)
  121. buttonFrame = CGRect(origin: CGPoint(x: 0, y: bounds.height - cornerButtonWidth), size: buttonSize)
  122. case 3: // Bottom Right
  123. verticalFrame = CGRect(x: bounds.width - cornerLineDepth - outterGap, y: bounds.height - cornerLineWidth - outterGap, width: cornerLineDepth, height: cornerLineWidth)
  124. horizontalFrame = CGRect(x: bounds.width - cornerLineWidth - outterGap, y: bounds.height - cornerLineDepth - outterGap, width: cornerLineWidth, height: cornerLineDepth)
  125. buttonFrame = CGRect(origin: CGPoint(x: bounds.width - cornerButtonWidth, y: bounds.height - cornerButtonWidth), size: buttonSize)
  126. default:
  127. verticalFrame = CGRect.zero
  128. horizontalFrame = CGRect.zero
  129. buttonFrame = CGRect.zero
  130. }
  131. corner[0].frame = verticalFrame
  132. corner[1].frame = horizontalFrame
  133. cornerButtons[i].frame = buttonFrame
  134. }
  135. }
  136. let lineThickness = lineWidth / UIScreen.main.scale
  137. let vPadding = (bounds.height - outterGap * 2 - (lineThickness * CGFloat(horizontalLines.count))) / CGFloat(horizontalLines.count + 1)
  138. let hPadding = (bounds.width - outterGap * 2 - (lineThickness * CGFloat(verticalLines.count))) / CGFloat(verticalLines.count + 1)
  139. for i in 0..<horizontalLines.count {
  140. let hLine = horizontalLines[i]
  141. let vLine = verticalLines[i]
  142. let vSpacing = (vPadding * CGFloat(i + 1)) + (lineThickness * CGFloat(i))
  143. let hSpacing = (hPadding * CGFloat(i + 1)) + (lineThickness * CGFloat(i))
  144. hLine.frame = CGRect(x: outterGap, y: vSpacing + outterGap, width: bounds.width - outterGap * 2, height: lineThickness)
  145. vLine.frame = CGRect(x: hSpacing + outterGap, y: outterGap, width: lineThickness, height: bounds.height - outterGap * 2)
  146. }
  147. }
  148. func createLines() {
  149. outerLines = [createLine(), createLine(), createLine(), createLine()]
  150. horizontalLines = [createLine(), createLine()]
  151. verticalLines = [createLine(), createLine()]
  152. topLeftCornerLines = [createLine(), createLine()]
  153. topRightCornerLines = [createLine(), createLine()]
  154. bottomLeftCornerLines = [createLine(), createLine()]
  155. bottomRightCornerLines = [createLine(), createLine()]
  156. cornerButtons = [createButton(), createButton(), createButton(), createButton()]
  157. let dragGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(moveCropOverlay))
  158. addGestureRecognizer(dragGestureRecognizer)
  159. }
  160. func createLine() -> UIView {
  161. let line = UIView()
  162. line.backgroundColor = UIColor.white
  163. addSubview(line)
  164. return line
  165. }
  166. func createButton() -> UIButton {
  167. let button = UIButton()
  168. button.backgroundColor = UIColor.clear
  169. let dragGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(moveCropOverlay))
  170. button.addGestureRecognizer(dragGestureRecognizer)
  171. addSubview(button)
  172. return button
  173. }
  174. @objc func moveCropOverlay(gestureRecognizer: UIPanGestureRecognizer) {
  175. if isResizable, showsButtons, let button = gestureRecognizer.view as? UIButton {
  176. if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
  177. let translation = gestureRecognizer.translation(in: self)
  178. var newFrame: CGRect
  179. switch button {
  180. case cornerButtons[0]: // Top Left
  181. newFrame = CGRect(x: frame.origin.x + translation.x, y: frame.origin.y + translation.y, width: frame.size.width - translation.x, height: frame.size.height - translation.y)
  182. case cornerButtons[1]: // Top Right
  183. newFrame = CGRect(x: frame.origin.x, y: frame.origin.y + translation.y, width: frame.size.width + translation.x, height: frame.size.height - translation.y)
  184. case cornerButtons[2]: // Bottom Left
  185. newFrame = CGRect(x: frame.origin.x + translation.x, y: frame.origin.y, width: frame.size.width - translation.x, height: frame.size.height + translation.y)
  186. case cornerButtons[3]: // Bottom Right
  187. newFrame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width + translation.x, height: frame.size.height + translation.y)
  188. default:
  189. newFrame = CGRect.zero
  190. }
  191. let minimumFrame = CGRect(x: newFrame.origin.x, y: newFrame.origin.y, width: max(newFrame.size.width, minimumSize.width + 2 * outterGap), height: max(newFrame.size.height, minimumSize.height + 2 * outterGap))
  192. frame = minimumFrame
  193. layoutSubviews()
  194. gestureRecognizer.setTranslation(CGPoint.zero, in: self)
  195. }
  196. } else if isMovable {
  197. if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
  198. let translation = gestureRecognizer.translation(in: self)
  199. gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
  200. gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self)
  201. }
  202. }
  203. }
  204. override func hitTest(_ hitPoint: CGPoint, with event: UIEvent?) -> UIView? {
  205. if let view = super.hitTest(hitPoint, with: event) {
  206. if isResizable && showsButtons {
  207. let isButton = cornerButtons.reduce(false) { $1.hitTest(convert(hitPoint, to: $1), with: event) != nil || $0 }
  208. if isButton { //user hit a resize button, so use our view
  209. return view
  210. }
  211. }
  212. if isMovable {
  213. let centerPoint = CGPoint(x: bounds.midX, y: bounds.midY)
  214. if centerPoint.distance(to: hitPoint) < centerCircleRadius {
  215. return view //user is in the center of the view, wants to move it, so use our view
  216. }
  217. }
  218. }
  219. return nil
  220. }
  221. }
  222. extension CGPoint {
  223. func distance(to point: CGPoint) -> CGFloat {
  224. return sqrt(pow((point.x - x), 2) + pow((point.y - y), 2))
  225. }
  226. }