initial
This commit is contained in:
376
Pods/ZLPhotoBrowser/Sources/Edit/ZLBaseStickerView.swift
generated
Normal file
376
Pods/ZLPhotoBrowser/Sources/Edit/ZLBaseStickerView.swift
generated
Normal 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~360,并对360取余
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user