This commit is contained in:
DDIsFriend
2023-08-18 17:28:57 +08:00
commit f0e8a1709d
4282 changed files with 192396 additions and 0 deletions

View File

@@ -0,0 +1,376 @@
//
// 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
}
}