Files
OrderScheduling/Pods/ZLPhotoBrowser/Sources/Edit/ZLBaseStickerView.swift
DDIsFriend f0e8a1709d initial
2023-08-18 17:28:57 +08:00

377 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// ZLBaseStickerView.swift
// ZLPhotoBrowser
//
// Created by long on 2022/11/28.
//
import UIKit
protocol ZLStickerViewDelegate: NSObject {
// Called when scale or rotate or move.
func stickerBeginOperation(_ sticker: UIView)
// Called during scale or rotate or move.
func stickerOnOperation(_ sticker: UIView, panGes: UIPanGestureRecognizer)
// Called after scale or rotate or move.
func stickerEndOperation(_ sticker: UIView, panGes: UIPanGestureRecognizer)
// Called when tap sticker.
func stickerDidTap(_ sticker: UIView)
func sticker(_ textSticker: ZLTextStickerView, editText text: String)
}
protocol ZLStickerViewAdditional: NSObject {
var gesIsEnabled: Bool { get set }
func resetState()
func moveToAshbin()
func addScale(_ scale: CGFloat)
}
class ZLBaseStickerView<T>: UIView, UIGestureRecognizerDelegate {
private enum Direction: Int {
case up = 0
case right = 90
case bottom = 180
case left = 270
}
var borderWidth = 1 / UIScreen.main.scale
var firstLayout = true
let originScale: CGFloat
let originAngle: CGFloat
var maxGesScale: CGFloat
var originTransform: CGAffineTransform = .identity
var timer: Timer?
var totalTranslationPoint: CGPoint = .zero
var gesTranslationPoint: CGPoint = .zero
var gesRotation: CGFloat = 0
var gesScale: CGFloat = 1
var onOperation = false
var gesIsEnabled = true
var originFrame: CGRect
lazy var tapGes = UITapGestureRecognizer(target: self, action: #selector(tapAction(_:)))
lazy var pinchGes: UIPinchGestureRecognizer = {
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(pinchAction(_:)))
pinch.delegate = self
return pinch
}()
lazy var panGes: UIPanGestureRecognizer = {
let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction(_:)))
pan.delegate = self
return pan
}()
var state: T {
fatalError()
}
var borderView: UIView {
return self
}
weak var delegate: ZLStickerViewDelegate?
deinit {
cleanTimer()
}
init(
originScale: CGFloat,
originAngle: CGFloat,
originFrame: CGRect,
gesScale: CGFloat = 1,
gesRotation: CGFloat = 0,
totalTranslationPoint: CGPoint = .zero,
showBorder: Bool = true
) {
self.originScale = originScale
self.originAngle = originAngle
self.originFrame = originFrame
self.maxGesScale = 4 / originScale
super.init(frame: .zero)
self.gesScale = gesScale
self.gesRotation = gesRotation
self.totalTranslationPoint = totalTranslationPoint
borderView.layer.borderWidth = borderWidth
hideBorder()
if showBorder {
startTimer()
}
addGestureRecognizer(tapGes)
addGestureRecognizer(pinchGes)
let rotationGes = UIRotationGestureRecognizer(target: self, action: #selector(rotationAction(_:)))
rotationGes.delegate = self
addGestureRecognizer(rotationGes)
addGestureRecognizer(panGes)
tapGes.require(toFail: panGes)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
guard firstLayout else {
return
}
// Rotate must be first when first layout.
transform = transform.rotated(by: originAngle.zl.toPi)
if totalTranslationPoint != .zero {
let direction = direction(for: originAngle)
if direction == .right {
transform = transform.translatedBy(x: totalTranslationPoint.y, y: -totalTranslationPoint.x)
} else if direction == .bottom {
transform = transform.translatedBy(x: -totalTranslationPoint.x, y: -totalTranslationPoint.y)
} else if direction == .left {
transform = transform.translatedBy(x: -totalTranslationPoint.y, y: totalTranslationPoint.x)
} else {
transform = transform.translatedBy(x: totalTranslationPoint.x, y: totalTranslationPoint.y)
}
}
transform = transform.scaledBy(x: originScale, y: originScale)
originTransform = transform
if gesScale != 1 {
transform = transform.scaledBy(x: gesScale, y: gesScale)
}
if gesRotation != 0 {
transform = transform.rotated(by: gesRotation)
}
firstLayout = false
setupUIFrameWhenFirstLayout()
}
func setupUIFrameWhenFirstLayout() {}
private func direction(for angle: CGFloat) -> ZLBaseStickerView.Direction {
// 0~360360
let angle = ((Int(angle) % 360) + 360) % 360
return ZLBaseStickerView.Direction(rawValue: angle) ?? .up
}
@objc func tapAction(_ ges: UITapGestureRecognizer) {
guard gesIsEnabled else { return }
superview?.bringSubviewToFront(self)
delegate?.stickerDidTap(self)
startTimer()
}
@objc func pinchAction(_ ges: UIPinchGestureRecognizer) {
guard gesIsEnabled else { return }
let scale = min(maxGesScale, gesScale * ges.scale)
ges.scale = 1
guard scale != gesScale else {
return
}
gesScale = scale
if ges.state == .began {
setOperation(true)
} else if ges.state == .changed {
updateTransform()
} else if ges.state == .ended || ges.state == .cancelled {
setOperation(false)
}
}
@objc func rotationAction(_ ges: UIRotationGestureRecognizer) {
guard gesIsEnabled else { return }
gesRotation += ges.rotation
ges.rotation = 0
if ges.state == .began {
setOperation(true)
} else if ges.state == .changed {
updateTransform()
} else if ges.state == .ended || ges.state == .cancelled {
setOperation(false)
}
}
@objc func panAction(_ ges: UIPanGestureRecognizer) {
guard gesIsEnabled else { return }
let point = ges.translation(in: superview)
gesTranslationPoint = CGPoint(x: point.x / originScale, y: point.y / originScale)
if ges.state == .began {
setOperation(true)
} else if ges.state == .changed {
updateTransform()
} else if ges.state == .ended || ges.state == .cancelled {
totalTranslationPoint.x += point.x
totalTranslationPoint.y += point.y
setOperation(false)
let direction = direction(for: originAngle)
if direction == .right {
originTransform = originTransform.translatedBy(x: gesTranslationPoint.y, y: -gesTranslationPoint.x)
} else if direction == .bottom {
originTransform = originTransform.translatedBy(x: -gesTranslationPoint.x, y: -gesTranslationPoint.y)
} else if direction == .left {
originTransform = originTransform.translatedBy(x: -gesTranslationPoint.y, y: gesTranslationPoint.x)
} else {
originTransform = originTransform.translatedBy(x: gesTranslationPoint.x, y: gesTranslationPoint.y)
}
gesTranslationPoint = .zero
}
}
func setOperation(_ isOn: Bool) {
if isOn, !onOperation {
onOperation = true
cleanTimer()
borderView.layer.borderColor = UIColor.white.cgColor
superview?.bringSubviewToFront(self)
delegate?.stickerBeginOperation(self)
} else if !isOn, onOperation {
onOperation = false
startTimer()
delegate?.stickerEndOperation(self, panGes: panGes)
}
}
func updateTransform() {
var transform = originTransform
let direction = direction(for: originAngle)
if direction == .right {
transform = transform.translatedBy(x: gesTranslationPoint.y, y: -gesTranslationPoint.x)
} else if direction == .bottom {
transform = transform.translatedBy(x: -gesTranslationPoint.x, y: -gesTranslationPoint.y)
} else if direction == .left {
transform = transform.translatedBy(x: -gesTranslationPoint.y, y: gesTranslationPoint.x)
} else {
transform = transform.translatedBy(x: gesTranslationPoint.x, y: gesTranslationPoint.y)
}
// Scale must after translate.
transform = transform.scaledBy(x: gesScale, y: gesScale)
// Rotate must after scale.
transform = transform.rotated(by: gesRotation)
self.transform = transform
delegate?.stickerOnOperation(self, panGes: panGes)
}
@objc private func hideBorder() {
borderView.layer.borderColor = UIColor.clear.cgColor
}
func startTimer() {
cleanTimer()
borderView.layer.borderColor = UIColor.white.cgColor
timer = Timer.scheduledTimer(timeInterval: 2, target: ZLWeakProxy(target: self), selector: #selector(hideBorder), userInfo: nil, repeats: false)
RunLoop.current.add(timer!, forMode: .common)
}
private func cleanTimer() {
timer?.invalidate()
timer = nil
}
// MARK: UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
extension ZLBaseStickerView: ZLStickerViewAdditional {
func resetState() {
onOperation = false
cleanTimer()
hideBorder()
}
func moveToAshbin() {
cleanTimer()
removeFromSuperview()
}
func addScale(_ scale: CGFloat) {
// Revert zoom scale.
transform = transform.scaledBy(x: 1 / originScale, y: 1 / originScale)
// Revert ges scale.
transform = transform.scaledBy(x: 1 / gesScale, y: 1 / gesScale)
// Revert ges rotation.
transform = transform.rotated(by: -gesRotation)
var origin = frame.origin
origin.x *= scale
origin.y *= scale
let newSize = CGSize(width: frame.width * scale, height: frame.height * scale)
let newOrigin = CGPoint(x: frame.minX + (frame.width - newSize.width) / 2, y: frame.minY + (frame.height - newSize.height) / 2)
let diffX: CGFloat = (origin.x - newOrigin.x)
let diffY: CGFloat = (origin.y - newOrigin.y)
let direction = direction(for: originScale)
if direction == .right {
transform = transform.translatedBy(x: diffY, y: -diffX)
originTransform = originTransform.translatedBy(x: diffY / originScale, y: -diffX / originScale)
} else if direction == .bottom {
transform = transform.translatedBy(x: -diffX, y: -diffY)
originTransform = originTransform.translatedBy(x: -diffX / originScale, y: -diffY / originScale)
} else if direction == .left {
transform = transform.translatedBy(x: -diffY, y: diffX)
originTransform = originTransform.translatedBy(x: -diffY / originScale, y: diffX / originScale)
} else {
transform = transform.translatedBy(x: diffX, y: diffY)
originTransform = originTransform.translatedBy(x: diffX / originScale, y: diffY / originScale)
}
totalTranslationPoint.x += diffX
totalTranslationPoint.y += diffY
transform = transform.scaledBy(x: scale, y: scale)
// Readd zoom scale.
transform = transform.scaledBy(x: originScale, y: originScale)
// Readd ges scale.
transform = transform.scaledBy(x: gesScale, y: gesScale)
// Readd ges rotation.
transform = transform.rotated(by: gesRotation)
gesScale *= scale
maxGesScale *= scale
}
}