ZLTextStickerView.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. //
  2. // ZLTextStickerView.swift
  3. // ZLPhotoBrowser
  4. //
  5. // Created by long on 2020/10/30.
  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 ZLTextStickerViewDelegate: ZLStickerViewDelegate {
  28. func sticker(_ textSticker: ZLTextStickerView, editText text: String)
  29. }
  30. class ZLTextStickerView: UIView, ZLStickerViewAdditional {
  31. static let edgeInset: CGFloat = 20
  32. static let fontSize: CGFloat = 30
  33. static let borderWidth = 1 / UIScreen.main.scale
  34. weak var delegate: ZLTextStickerViewDelegate?
  35. var firstLayout = true
  36. var gesIsEnabled = true
  37. let originScale: CGFloat
  38. let originAngle: CGFloat
  39. var originFrame: CGRect
  40. var originTransform: CGAffineTransform = .identity
  41. var text: String {
  42. didSet {
  43. self.label.text = text
  44. }
  45. }
  46. var textColor: UIColor {
  47. didSet {
  48. label.textColor = textColor
  49. }
  50. }
  51. // TODO: add text background color
  52. var bgColor: UIColor {
  53. didSet {
  54. label.backgroundColor = bgColor
  55. }
  56. }
  57. var borderView: UIView!
  58. var label: UILabel!
  59. var pinchGes: UIPinchGestureRecognizer!
  60. var tapGes: UITapGestureRecognizer!
  61. var panGes: UIPanGestureRecognizer!
  62. var timer: Timer?
  63. var totalTranslationPoint: CGPoint = .zero
  64. var gesTranslationPoint: CGPoint = .zero
  65. var gesRotation: CGFloat = 0
  66. var gesScale: CGFloat = 1
  67. var onOperation = false
  68. // Conver all states to model.
  69. var state: ZLTextStickerState {
  70. return ZLTextStickerState(text: self.text, textColor: self.textColor, bgColor: self.bgColor, originScale: self.originScale, originAngle: self.originAngle, originFrame: self.originFrame, gesScale: self.gesScale, gesRotation: self.gesRotation, totalTranslationPoint: self.totalTranslationPoint)
  71. }
  72. deinit {
  73. zl_debugPrint("ZLTextStickerView deinit")
  74. self.cleanTimer()
  75. }
  76. convenience init(from state: ZLTextStickerState) {
  77. self.init(text: state.text, textColor: state.textColor, bgColor: state.bgColor, originScale: state.originScale, originAngle: state.originAngle, originFrame: state.originFrame, gesScale: state.gesScale, gesRotation: state.gesRotation, totalTranslationPoint: state.totalTranslationPoint, showBorder: false)
  78. }
  79. init(text: String, textColor: UIColor, bgColor: UIColor, originScale: CGFloat, originAngle: CGFloat, originFrame: CGRect, gesScale: CGFloat = 1, gesRotation: CGFloat = 0, totalTranslationPoint: CGPoint = .zero, showBorder: Bool = true) {
  80. self.originScale = originScale
  81. self.text = text
  82. self.textColor = textColor
  83. self.bgColor = bgColor
  84. self.originAngle = originAngle
  85. self.originFrame = originFrame
  86. super.init(frame: .zero)
  87. self.gesScale = gesScale
  88. self.gesRotation = gesRotation
  89. self.totalTranslationPoint = totalTranslationPoint
  90. self.borderView = UIView()
  91. self.borderView.layer.borderWidth = ZLTextStickerView.borderWidth
  92. self.hideBorder()
  93. if showBorder {
  94. self.startTimer()
  95. }
  96. self.addSubview(self.borderView)
  97. self.label = UILabel()
  98. self.label.text = text
  99. self.label.font = UIFont.boldSystemFont(ofSize: ZLTextStickerView.fontSize)
  100. self.label.textColor = textColor
  101. self.label.backgroundColor = bgColor
  102. self.label.numberOfLines = 0
  103. self.label.lineBreakMode = .byCharWrapping
  104. self.borderView.addSubview(self.label)
  105. self.tapGes = UITapGestureRecognizer(target: self, action: #selector(tapAction(_:)))
  106. self.addGestureRecognizer(self.tapGes)
  107. self.pinchGes = UIPinchGestureRecognizer(target: self, action: #selector(pinchAction(_:)))
  108. self.pinchGes.delegate = self
  109. self.addGestureRecognizer(self.pinchGes)
  110. let rotationGes = UIRotationGestureRecognizer(target: self, action: #selector(rotationAction(_:)))
  111. rotationGes.delegate = self
  112. self.addGestureRecognizer(rotationGes)
  113. self.panGes = UIPanGestureRecognizer(target: self, action: #selector(panAction(_:)))
  114. self.panGes.delegate = self
  115. self.addGestureRecognizer(self.panGes)
  116. self.tapGes.require(toFail: self.panGes)
  117. }
  118. required init?(coder: NSCoder) {
  119. fatalError("init(coder:) has not been implemented")
  120. }
  121. override func layoutSubviews() {
  122. super.layoutSubviews()
  123. guard self.firstLayout else {
  124. return
  125. }
  126. // Rotate must be first when first layout.
  127. self.transform = self.transform.rotated(by: self.originAngle.toPi)
  128. if self.totalTranslationPoint != .zero {
  129. if self.originAngle == 90 {
  130. self.transform = self.transform.translatedBy(x: self.totalTranslationPoint.y, y: -self.totalTranslationPoint.x)
  131. } else if self.originAngle == 180 {
  132. self.transform = self.transform.translatedBy(x: -self.totalTranslationPoint.x, y: -self.totalTranslationPoint.y)
  133. } else if self.originAngle == 270 {
  134. self.transform = self.transform.translatedBy(x: -self.totalTranslationPoint.y, y: self.totalTranslationPoint.x)
  135. } else {
  136. self.transform = self.transform.translatedBy(x: self.totalTranslationPoint.x, y: self.totalTranslationPoint.y)
  137. }
  138. }
  139. self.transform = self.transform.scaledBy(x: self.originScale, y: self.originScale)
  140. self.originTransform = self.transform
  141. if self.gesScale != 1 {
  142. self.transform = self.transform.scaledBy(x: self.gesScale, y: self.gesScale)
  143. }
  144. if self.gesRotation != 0 {
  145. self.transform = self.transform.rotated(by: self.gesRotation)
  146. }
  147. self.firstLayout = false
  148. self.borderView.frame = self.bounds.insetBy(dx: ZLTextStickerView.edgeInset, dy: ZLTextStickerView.edgeInset)
  149. self.label.frame = self.borderView.bounds.insetBy(dx: ZLTextStickerView.edgeInset, dy: ZLTextStickerView.edgeInset)
  150. }
  151. @objc func tapAction(_ ges: UITapGestureRecognizer) {
  152. guard self.gesIsEnabled else { return }
  153. if let t = self.timer, t.isValid {
  154. self.delegate?.sticker(self, editText: self.text)
  155. } else {
  156. self.superview?.bringSubviewToFront(self)
  157. self.delegate?.stickerDidTap(self)
  158. self.startTimer()
  159. }
  160. }
  161. @objc func pinchAction(_ ges: UIPinchGestureRecognizer) {
  162. guard self.gesIsEnabled else { return }
  163. self.gesScale *= ges.scale
  164. ges.scale = 1
  165. if ges.state == .began {
  166. self.setOperation(true)
  167. } else if ges.state == .changed {
  168. self.updateTransform()
  169. } else if (ges.state == .ended || ges.state == .cancelled){
  170. self.setOperation(false)
  171. }
  172. }
  173. @objc func rotationAction(_ ges: UIRotationGestureRecognizer) {
  174. guard self.gesIsEnabled else { return }
  175. self.gesRotation += ges.rotation
  176. ges.rotation = 0
  177. if ges.state == .began {
  178. self.setOperation(true)
  179. } else if ges.state == .changed {
  180. self.updateTransform()
  181. } else if (ges.state == .ended || ges.state == .cancelled){
  182. self.setOperation(false)
  183. }
  184. }
  185. @objc func panAction(_ ges: UIPanGestureRecognizer) {
  186. guard self.gesIsEnabled else { return }
  187. let point = ges.translation(in: self.superview)
  188. self.gesTranslationPoint = CGPoint(x: point.x / self.originScale, y: point.y / self.originScale)
  189. if ges.state == .began {
  190. self.setOperation(true)
  191. } else if ges.state == .changed {
  192. self.updateTransform()
  193. } else if (ges.state == .ended || ges.state == .cancelled) {
  194. self.totalTranslationPoint.x += point.x
  195. self.totalTranslationPoint.y += point.y
  196. self.setOperation(false)
  197. if self.originAngle == 90 {
  198. self.originTransform = self.originTransform.translatedBy(x: self.gesTranslationPoint.y, y: -self.gesTranslationPoint.x)
  199. } else if self.originAngle == 180 {
  200. self.originTransform = self.originTransform.translatedBy(x: -self.gesTranslationPoint.x, y: -self.gesTranslationPoint.y)
  201. } else if self.originAngle == 270 {
  202. self.originTransform = self.originTransform.translatedBy(x: -self.gesTranslationPoint.y, y: self.gesTranslationPoint.x)
  203. } else {
  204. self.originTransform = self.originTransform.translatedBy(x: self.gesTranslationPoint.x, y: self.gesTranslationPoint.y)
  205. }
  206. self.gesTranslationPoint = .zero
  207. }
  208. }
  209. func setOperation(_ isOn: Bool) {
  210. if isOn, !self.onOperation {
  211. self.onOperation = true
  212. self.cleanTimer()
  213. self.borderView.layer.borderColor = UIColor.white.cgColor
  214. self.superview?.bringSubviewToFront(self)
  215. self.delegate?.stickerBeginOperation(self)
  216. } else if !isOn, self.onOperation {
  217. self.onOperation = false
  218. self.startTimer()
  219. self.delegate?.stickerEndOperation(self, panGes: self.panGes)
  220. }
  221. }
  222. func updateTransform() {
  223. var transform = self.originTransform
  224. if self.originAngle == 90 {
  225. transform = transform.translatedBy(x: self.gesTranslationPoint.y, y: -self.gesTranslationPoint.x)
  226. } else if self.originAngle == 180 {
  227. transform = transform.translatedBy(x: -self.gesTranslationPoint.x, y: -self.gesTranslationPoint.y)
  228. } else if self.originAngle == 270 {
  229. transform = transform.translatedBy(x: -self.gesTranslationPoint.y, y: self.gesTranslationPoint.x)
  230. } else {
  231. transform = transform.translatedBy(x: self.gesTranslationPoint.x, y: self.gesTranslationPoint.y)
  232. }
  233. // Scale must after translate.
  234. transform = transform.scaledBy(x: self.gesScale, y: self.gesScale)
  235. // Rotate must after scale.
  236. transform = transform.rotated(by: self.gesRotation)
  237. self.transform = transform
  238. self.delegate?.stickerOnOperation(self, panGes: self.panGes)
  239. }
  240. @objc func hideBorder() {
  241. self.borderView.layer.borderColor = UIColor.clear.cgColor
  242. }
  243. func startTimer() {
  244. self.cleanTimer()
  245. self.borderView.layer.borderColor = UIColor.white.cgColor
  246. self.timer = Timer.scheduledTimer(timeInterval: 2, target: ZLWeakProxy(target: self), selector: #selector(hideBorder), userInfo: nil, repeats: false)
  247. RunLoop.current.add(self.timer!, forMode: .common)
  248. }
  249. func cleanTimer() {
  250. self.timer?.invalidate()
  251. self.timer = nil
  252. }
  253. func resetState() {
  254. self.onOperation = false
  255. self.cleanTimer()
  256. self.hideBorder()
  257. }
  258. func moveToAshbin() {
  259. self.cleanTimer()
  260. self.removeFromSuperview()
  261. }
  262. func addScale(_ scale: CGFloat) {
  263. // Revert zoom scale.
  264. self.transform = self.transform.scaledBy(x: 1/self.originScale, y: 1/self.originScale)
  265. // Revert ges scale.
  266. self.transform = self.transform.scaledBy(x: 1/self.gesScale, y: 1/self.gesScale)
  267. // Revert ges rotation.
  268. self.transform = self.transform.rotated(by: -self.gesRotation)
  269. var origin = self.frame.origin
  270. origin.x *= scale
  271. origin.y *= scale
  272. let newSize = CGSize(width: self.frame.width * scale, height: self.frame.height * scale)
  273. let newOrigin = CGPoint(x: self.frame.minX + (self.frame.width - newSize.width)/2, y: self.frame.minY + (self.frame.height - newSize.height)/2)
  274. let diffX: CGFloat = (origin.x - newOrigin.x)
  275. let diffY: CGFloat = (origin.y - newOrigin.y)
  276. if self.originAngle == 90 {
  277. self.transform = self.transform.translatedBy(x: diffY, y: -diffX)
  278. self.originTransform = self.originTransform.translatedBy(x: diffY / self.originScale, y: -diffX / self.originScale)
  279. } else if self.originAngle == 180 {
  280. self.transform = self.transform.translatedBy(x: -diffX, y: -diffY)
  281. self.originTransform = self.originTransform.translatedBy(x: -diffX / self.originScale, y: -diffY / self.originScale)
  282. } else if self.originAngle == 270 {
  283. self.transform = self.transform.translatedBy(x: -diffY, y: diffX)
  284. self.originTransform = self.originTransform.translatedBy(x: -diffY / self.originScale, y: diffX / self.originScale)
  285. } else {
  286. self.transform = self.transform.translatedBy(x: diffX, y: diffY)
  287. self.originTransform = self.originTransform.translatedBy(x: diffX / self.originScale, y: diffY / self.originScale)
  288. }
  289. self.totalTranslationPoint.x += diffX
  290. self.totalTranslationPoint.y += diffY
  291. self.transform = self.transform.scaledBy(x: scale, y: scale)
  292. // Readd zoom scale.
  293. self.transform = self.transform.scaledBy(x: self.originScale, y: self.originScale)
  294. // Readd ges scale.
  295. self.transform = self.transform.scaledBy(x: self.gesScale, y: self.gesScale)
  296. // Readd ges rotation.
  297. self.transform = self.transform.rotated(by: self.gesRotation)
  298. self.gesScale *= scale
  299. }
  300. func changeSize(to newSize: CGSize) {
  301. // Revert zoom scale.
  302. self.transform = self.transform.scaledBy(x: 1/self.originScale, y: 1/self.originScale)
  303. // Revert ges scale.
  304. self.transform = self.transform.scaledBy(x: 1/self.gesScale, y: 1/self.gesScale)
  305. // Revert ges rotation.
  306. self.transform = self.transform.rotated(by: -self.gesRotation)
  307. self.transform = self.transform.rotated(by: -self.originAngle.toPi)
  308. // Recalculate current frame.
  309. let center = CGPoint(x: self.frame.midX, y: self.frame.midY)
  310. var frame = self.frame
  311. frame.origin.x = center.x - newSize.width / 2
  312. frame.origin.y = center.y - newSize.height / 2
  313. frame.size = newSize
  314. self.frame = frame
  315. let oc = CGPoint(x: self.originFrame.midX, y: self.originFrame.midY)
  316. var of = self.originFrame
  317. of.origin.x = oc.x - newSize.width / 2
  318. of.origin.y = oc.y - newSize.height / 2
  319. of.size = newSize
  320. self.originFrame = of
  321. self.borderView.frame = self.bounds.insetBy(dx: ZLTextStickerView.edgeInset, dy: ZLTextStickerView.edgeInset)
  322. self.label.frame = self.borderView.bounds.insetBy(dx: ZLTextStickerView.edgeInset, dy: ZLTextStickerView.edgeInset)
  323. // Readd zoom scale.
  324. self.transform = self.transform.scaledBy(x: self.originScale, y: self.originScale)
  325. // Readd ges scale.
  326. self.transform = self.transform.scaledBy(x: self.gesScale, y: self.gesScale)
  327. // Readd ges rotation.
  328. self.transform = self.transform.rotated(by: self.gesRotation)
  329. self.transform = self.transform.rotated(by: self.originAngle.toPi)
  330. }
  331. class func calculateSize(text: String, width: CGFloat) -> CGSize {
  332. let diff = ZLTextStickerView.edgeInset * 2
  333. let size = text.boundingRect(font: UIFont.boldSystemFont(ofSize: ZLTextStickerView.fontSize), limitSize: CGSize(width: width - diff, height: CGFloat.greatestFiniteMagnitude))
  334. return CGSize(width: size.width + diff * 2, height: size.height + diff * 2)
  335. }
  336. }
  337. extension ZLTextStickerView: UIGestureRecognizerDelegate {
  338. func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
  339. return true
  340. }
  341. }
  342. public class ZLTextStickerState: NSObject {
  343. let text: String
  344. let textColor: UIColor
  345. let bgColor: UIColor
  346. let originScale: CGFloat
  347. let originAngle: CGFloat
  348. let originFrame: CGRect
  349. let gesScale: CGFloat
  350. let gesRotation: CGFloat
  351. let totalTranslationPoint: CGPoint
  352. init(text: String, textColor: UIColor, bgColor: UIColor, originScale: CGFloat, originAngle: CGFloat, originFrame: CGRect, gesScale: CGFloat, gesRotation: CGFloat, totalTranslationPoint: CGPoint) {
  353. self.text = text
  354. self.textColor = textColor
  355. self.bgColor = bgColor
  356. self.originScale = originScale
  357. self.originAngle = originAngle
  358. self.originFrame = originFrame
  359. self.gesScale = gesScale
  360. self.gesRotation = gesRotation
  361. self.totalTranslationPoint = totalTranslationPoint
  362. super.init()
  363. }
  364. }