ZLImageStickerView.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. //
  2. // ZLImageStickerView.swift
  3. // ZLPhotoBrowser
  4. //
  5. // Created by long on 2020/11/20.
  6. //
  7. // Copyright (c) 2020 Long Zhang <495181165@qq.com>
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. import UIKit
  27. protocol ZLStickerViewDelegate: NSObject {
  28. // Called when scale or rotate or move.
  29. func stickerBeginOperation(_ sticker: UIView)
  30. // Called during scale or rotate or move.
  31. func stickerOnOperation(_ sticker: UIView, panGes: UIPanGestureRecognizer)
  32. // Called after scale or rotate or move.
  33. func stickerEndOperation(_ sticker: UIView, panGes: UIPanGestureRecognizer)
  34. // Called when tap sticker.
  35. func stickerDidTap(_ sticker: UIView)
  36. }
  37. protocol ZLStickerViewAdditional: NSObject {
  38. var gesIsEnabled: Bool { get set }
  39. func resetState()
  40. func moveToAshbin()
  41. func addScale(_ scale: CGFloat)
  42. }
  43. class ZLImageStickerView: UIView, ZLStickerViewAdditional {
  44. static let edgeInset: CGFloat = 20
  45. static let borderWidth = 1 / UIScreen.main.scale
  46. weak var delegate: ZLStickerViewDelegate?
  47. var firstLayout = true
  48. var gesIsEnabled = true
  49. let originScale: CGFloat
  50. let originAngle: CGFloat
  51. var originFrame: CGRect
  52. var originTransform: CGAffineTransform = .identity
  53. let image: UIImage
  54. var pinchGes: UIPinchGestureRecognizer!
  55. var tapGes: UITapGestureRecognizer!
  56. var panGes: UIPanGestureRecognizer!
  57. var timer: Timer?
  58. var imageView: UIImageView!
  59. var totalTranslationPoint: CGPoint = .zero
  60. var gesTranslationPoint: CGPoint = .zero
  61. var gesRotation: CGFloat = 0
  62. var gesScale: CGFloat = 1
  63. var onOperation = false
  64. // Conver all states to model.
  65. var state: ZLImageStickerState {
  66. return ZLImageStickerState(image: self.image, originScale: self.originScale, originAngle: self.originAngle, originFrame: self.originFrame, gesScale: self.gesScale, gesRotation: self.gesRotation, totalTranslationPoint: self.totalTranslationPoint)
  67. }
  68. deinit {
  69. zl_debugPrint("ZLImageStickerView deinit")
  70. self.cleanTimer()
  71. }
  72. convenience init(from state: ZLImageStickerState) {
  73. self.init(image: state.image, originScale: state.originScale, originAngle: state.originAngle, originFrame: state.originFrame, gesScale: state.gesScale, gesRotation: state.gesRotation, totalTranslationPoint: state.totalTranslationPoint, showBorder: false)
  74. }
  75. init(image: UIImage, originScale: CGFloat, originAngle: CGFloat, originFrame: CGRect, gesScale: CGFloat = 1, gesRotation: CGFloat = 0, totalTranslationPoint: CGPoint = .zero, showBorder: Bool = true) {
  76. self.image = image
  77. self.originScale = originScale
  78. self.originAngle = originAngle
  79. self.originFrame = originFrame
  80. super.init(frame: .zero)
  81. self.gesScale = gesScale
  82. self.gesRotation = gesRotation
  83. self.totalTranslationPoint = totalTranslationPoint
  84. self.layer.borderWidth = ZLTextStickerView.borderWidth
  85. self.hideBorder()
  86. if showBorder {
  87. self.startTimer()
  88. }
  89. self.imageView = UIImageView(image: image)
  90. self.imageView.contentMode = .scaleAspectFit
  91. self.imageView.clipsToBounds = true
  92. self.addSubview(self.imageView)
  93. self.tapGes = UITapGestureRecognizer(target: self, action: #selector(tapAction(_:)))
  94. self.addGestureRecognizer(self.tapGes)
  95. self.pinchGes = UIPinchGestureRecognizer(target: self, action: #selector(pinchAction(_:)))
  96. self.pinchGes.delegate = self
  97. self.addGestureRecognizer(self.pinchGes)
  98. let rotationGes = UIRotationGestureRecognizer(target: self, action: #selector(rotationAction(_:)))
  99. rotationGes.delegate = self
  100. self.addGestureRecognizer(rotationGes)
  101. self.panGes = UIPanGestureRecognizer(target: self, action: #selector(panAction(_:)))
  102. self.panGes.delegate = self
  103. self.addGestureRecognizer(self.panGes)
  104. self.tapGes.require(toFail: self.panGes)
  105. }
  106. required init?(coder: NSCoder) {
  107. fatalError("init(coder:) has not been implemented")
  108. }
  109. override func layoutSubviews() {
  110. super.layoutSubviews()
  111. guard self.firstLayout else {
  112. return
  113. }
  114. // Rotate must be first when first layout.
  115. self.transform = self.transform.rotated(by: self.originAngle.toPi)
  116. if self.totalTranslationPoint != .zero {
  117. if self.originAngle == 90 {
  118. self.transform = self.transform.translatedBy(x: self.totalTranslationPoint.y, y: -self.totalTranslationPoint.x)
  119. } else if self.originAngle == 180 {
  120. self.transform = self.transform.translatedBy(x: -self.totalTranslationPoint.x, y: -self.totalTranslationPoint.y)
  121. } else if self.originAngle == 270 {
  122. self.transform = self.transform.translatedBy(x: -self.totalTranslationPoint.y, y: self.totalTranslationPoint.x)
  123. } else {
  124. self.transform = self.transform.translatedBy(x: self.totalTranslationPoint.x, y: self.totalTranslationPoint.y)
  125. }
  126. }
  127. self.transform = self.transform.scaledBy(x: self.originScale, y: self.originScale)
  128. self.originTransform = self.transform
  129. if self.gesScale != 1 {
  130. self.transform = self.transform.scaledBy(x: self.gesScale, y: self.gesScale)
  131. }
  132. if self.gesRotation != 0 {
  133. self.transform = self.transform.rotated(by: self.gesRotation)
  134. }
  135. self.firstLayout = false
  136. self.imageView.frame = self.bounds.insetBy(dx: ZLImageStickerView.edgeInset, dy: ZLImageStickerView.edgeInset)
  137. }
  138. @objc func tapAction(_ ges: UITapGestureRecognizer) {
  139. guard self.gesIsEnabled else { return }
  140. self.superview?.bringSubviewToFront(self)
  141. self.delegate?.stickerDidTap(self)
  142. self.startTimer()
  143. }
  144. @objc func pinchAction(_ ges: UIPinchGestureRecognizer) {
  145. guard self.gesIsEnabled else { return }
  146. self.gesScale *= ges.scale
  147. ges.scale = 1
  148. if ges.state == .began {
  149. self.setOperation(true)
  150. } else if ges.state == .changed {
  151. self.updateTransform()
  152. } else if (ges.state == .ended || ges.state == .cancelled){
  153. self.setOperation(false)
  154. }
  155. }
  156. @objc func rotationAction(_ ges: UIRotationGestureRecognizer) {
  157. guard self.gesIsEnabled else { return }
  158. self.gesRotation += ges.rotation
  159. ges.rotation = 0
  160. if ges.state == .began {
  161. self.setOperation(true)
  162. } else if ges.state == .changed {
  163. self.updateTransform()
  164. } else if (ges.state == .ended || ges.state == .cancelled){
  165. self.setOperation(false)
  166. }
  167. }
  168. @objc func panAction(_ ges: UIPanGestureRecognizer) {
  169. guard self.gesIsEnabled else { return }
  170. let point = ges.translation(in: self.superview)
  171. self.gesTranslationPoint = CGPoint(x: point.x / self.originScale, y: point.y / self.originScale)
  172. if ges.state == .began {
  173. self.setOperation(true)
  174. } else if ges.state == .changed {
  175. self.updateTransform()
  176. } else if (ges.state == .ended || ges.state == .cancelled) {
  177. self.totalTranslationPoint.x += point.x
  178. self.totalTranslationPoint.y += point.y
  179. self.setOperation(false)
  180. if self.originAngle == 90 {
  181. self.originTransform = self.originTransform.translatedBy(x: self.gesTranslationPoint.y, y: -self.gesTranslationPoint.x)
  182. } else if self.originAngle == 180 {
  183. self.originTransform = self.originTransform.translatedBy(x: -self.gesTranslationPoint.x, y: -self.gesTranslationPoint.y)
  184. } else if self.originAngle == 270 {
  185. self.originTransform = self.originTransform.translatedBy(x: -self.gesTranslationPoint.y, y: self.gesTranslationPoint.x)
  186. } else {
  187. self.originTransform = self.originTransform.translatedBy(x: self.gesTranslationPoint.x, y: self.gesTranslationPoint.y)
  188. }
  189. self.gesTranslationPoint = .zero
  190. }
  191. }
  192. func setOperation(_ isOn: Bool) {
  193. if isOn, !self.onOperation {
  194. self.onOperation = true
  195. self.cleanTimer()
  196. self.layer.borderColor = UIColor.white.cgColor
  197. self.superview?.bringSubviewToFront(self)
  198. self.delegate?.stickerBeginOperation(self)
  199. } else if !isOn, self.onOperation {
  200. self.onOperation = false
  201. self.startTimer()
  202. self.delegate?.stickerEndOperation(self, panGes: self.panGes)
  203. }
  204. }
  205. func updateTransform() {
  206. var transform = self.originTransform
  207. if self.originAngle == 90 {
  208. transform = transform.translatedBy(x: self.gesTranslationPoint.y, y: -self.gesTranslationPoint.x)
  209. } else if self.originAngle == 180 {
  210. transform = transform.translatedBy(x: -self.gesTranslationPoint.x, y: -self.gesTranslationPoint.y)
  211. } else if self.originAngle == 270 {
  212. transform = transform.translatedBy(x: -self.gesTranslationPoint.y, y: self.gesTranslationPoint.x)
  213. } else {
  214. transform = transform.translatedBy(x: self.gesTranslationPoint.x, y: self.gesTranslationPoint.y)
  215. }
  216. // Scale must after translate.
  217. transform = transform.scaledBy(x: self.gesScale, y: self.gesScale)
  218. // Rotate must after scale.
  219. transform = transform.rotated(by: self.gesRotation)
  220. self.transform = transform
  221. self.delegate?.stickerOnOperation(self, panGes: self.panGes)
  222. }
  223. @objc func hideBorder() {
  224. self.layer.borderColor = UIColor.clear.cgColor
  225. }
  226. func startTimer() {
  227. self.cleanTimer()
  228. self.layer.borderColor = UIColor.white.cgColor
  229. self.timer = Timer.scheduledTimer(timeInterval: 2, target: ZLWeakProxy(target: self), selector: #selector(hideBorder), userInfo: nil, repeats: false)
  230. RunLoop.current.add(self.timer!, forMode: .default)
  231. }
  232. func cleanTimer() {
  233. self.timer?.invalidate()
  234. self.timer = nil
  235. }
  236. func resetState() {
  237. self.onOperation = false
  238. self.cleanTimer()
  239. self.hideBorder()
  240. }
  241. func moveToAshbin() {
  242. self.cleanTimer()
  243. self.removeFromSuperview()
  244. }
  245. func addScale(_ scale: CGFloat) {
  246. // Revert zoom scale.
  247. self.transform = self.transform.scaledBy(x: 1/self.originScale, y: 1/self.originScale)
  248. // Revert ges scale.
  249. self.transform = self.transform.scaledBy(x: 1/self.gesScale, y: 1/self.gesScale)
  250. // Revert ges rotation.
  251. self.transform = self.transform.rotated(by: -self.gesRotation)
  252. var origin = self.frame.origin
  253. origin.x *= scale
  254. origin.y *= scale
  255. let newSize = CGSize(width: self.frame.width * scale, height: self.frame.height * scale)
  256. let newOrigin = CGPoint(x: self.frame.minX + (self.frame.width - newSize.width)/2, y: self.frame.minY + (self.frame.height - newSize.height)/2)
  257. let diffX: CGFloat = (origin.x - newOrigin.x)
  258. let diffY: CGFloat = (origin.y - newOrigin.y)
  259. if self.originAngle == 90 {
  260. self.transform = self.transform.translatedBy(x: diffY, y: -diffX)
  261. self.originTransform = self.originTransform.translatedBy(x: diffY / self.originScale, y: -diffX / self.originScale)
  262. } else if self.originAngle == 180 {
  263. self.transform = self.transform.translatedBy(x: -diffX, y: -diffY)
  264. self.originTransform = self.originTransform.translatedBy(x: -diffX / self.originScale, y: -diffY / self.originScale)
  265. } else if self.originAngle == 270 {
  266. self.transform = self.transform.translatedBy(x: -diffY, y: diffX)
  267. self.originTransform = self.originTransform.translatedBy(x: -diffY / self.originScale, y: diffX / self.originScale)
  268. } else {
  269. self.transform = self.transform.translatedBy(x: diffX, y: diffY)
  270. self.originTransform = self.originTransform.translatedBy(x: diffX / self.originScale, y: diffY / self.originScale)
  271. }
  272. self.totalTranslationPoint.x += diffX
  273. self.totalTranslationPoint.y += diffY
  274. self.transform = self.transform.scaledBy(x: scale, y: scale)
  275. // Readd zoom scale.
  276. self.transform = self.transform.scaledBy(x: self.originScale, y: self.originScale)
  277. // Readd ges scale.
  278. self.transform = self.transform.scaledBy(x: self.gesScale, y: self.gesScale)
  279. // Readd ges rotation.
  280. self.transform = self.transform.rotated(by: self.gesRotation)
  281. self.gesScale *= scale
  282. }
  283. class func calculateSize(image: UIImage, width: CGFloat) -> CGSize {
  284. let maxSide = width / 2
  285. let minSide: CGFloat = 100
  286. let whRatio = image.size.width / image.size.height
  287. var size: CGSize = .zero
  288. if whRatio >= 1 {
  289. let w = min(maxSide, max(minSide, image.size.width))
  290. let h = w / whRatio
  291. size = CGSize(width: w, height: h)
  292. } else {
  293. let h = min(maxSide, max(minSide, image.size.width))
  294. let w = h * whRatio
  295. size = CGSize(width: w, height: h)
  296. }
  297. size.width += ZLImageStickerView.edgeInset * 2
  298. size.height += ZLImageStickerView.edgeInset * 2
  299. return size
  300. }
  301. }
  302. extension ZLImageStickerView: UIGestureRecognizerDelegate {
  303. func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
  304. return true
  305. }
  306. }
  307. public class ZLImageStickerState: NSObject {
  308. let image: UIImage
  309. let originScale: CGFloat
  310. let originAngle: CGFloat
  311. let originFrame: CGRect
  312. let gesScale: CGFloat
  313. let gesRotation: CGFloat
  314. let totalTranslationPoint: CGPoint
  315. init(image: UIImage, originScale: CGFloat, originAngle: CGFloat, originFrame: CGRect, gesScale: CGFloat, gesRotation: CGFloat, totalTranslationPoint: CGPoint) {
  316. self.image = image
  317. self.originScale = originScale
  318. self.originAngle = originAngle
  319. self.originFrame = originFrame
  320. self.gesScale = gesScale
  321. self.gesRotation = gesRotation
  322. self.totalTranslationPoint = totalTranslationPoint
  323. super.init()
  324. }
  325. }