637 lines
21 KiB
Swift
637 lines
21 KiB
Swift
//
|
|
// ReceiveMessagePopView.swift
|
|
// QuickLocation
|
|
//
|
|
|
|
import UIKit
|
|
|
|
struct ReceiveMessageDisplayItem {
|
|
let senderName: String
|
|
let avatar: UIImage?
|
|
let image: UIImage?
|
|
let message: String
|
|
}
|
|
|
|
final class ReceiveMessagePopView: UIView {
|
|
|
|
private static let shared = ReceiveMessagePopView(
|
|
frame: CGRect(origin: .zero, size: kScreenSize)
|
|
)
|
|
|
|
private var messages: [ReceiveMessageDisplayItem] = []
|
|
private var item: ReceiveMessageDisplayItem?
|
|
private var onBurn: ((ReceiveMessageDisplayItem) -> Void)?
|
|
private var isBurning = false
|
|
private var effectLayers: [CALayer] = []
|
|
private var burnDisplayLink: CADisplayLink?
|
|
private var burnStartTime: CFTimeInterval = 0
|
|
private let burnDuration: CFTimeInterval = 1.05
|
|
private var burnLetterFrame = CGRect.zero
|
|
private var burnMaskLayer: CAShapeLayer?
|
|
private var charEdgeLayer: CAShapeLayer?
|
|
private var fireEdgeLayer: CAShapeLayer?
|
|
private var hotEdgeLayer: CAShapeLayer?
|
|
private var burnEmitterLayer: CAEmitterLayer?
|
|
|
|
static func show(
|
|
item: ReceiveMessageDisplayItem,
|
|
onBurn: ((ReceiveMessageDisplayItem) -> Void)? = nil
|
|
) {
|
|
show(items: [item], onBurn: onBurn)
|
|
}
|
|
|
|
static func show(
|
|
items: [ReceiveMessageDisplayItem],
|
|
onBurn: ((ReceiveMessageDisplayItem) -> Void)? = nil
|
|
) {
|
|
guard let firstItem = items.first, let window = kKeyWindow else { return }
|
|
|
|
let popup = ReceiveMessagePopView.shared
|
|
popup.removeFromSuperview()
|
|
popup.resetBurnState()
|
|
popup.messages = items
|
|
popup.item = firstItem
|
|
popup.onBurn = onBurn
|
|
popup.configure(item: firstItem)
|
|
|
|
window.addSubview(popup)
|
|
window.bringSubviewToFront(popup)
|
|
popup.layoutIfNeeded()
|
|
|
|
popup.overlayView.alpha = 0
|
|
popup.letterView.alpha = 0
|
|
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut]) {
|
|
popup.overlayView.alpha = 1
|
|
}
|
|
popup.animateInitialFlyIn()
|
|
}
|
|
|
|
private let overlayView: UIView = {
|
|
let view = UIView()
|
|
view.backgroundColor = UIColor.black.withAlphaComponent(0.62)
|
|
return view
|
|
}()
|
|
|
|
private let letterView = UIView()
|
|
|
|
private let cardBackgroundView: UIImageView = {
|
|
let view = UIImageView(image: UIImage(named: "Home/receive_message_card"))
|
|
view.contentMode = .scaleToFill
|
|
return view
|
|
}()
|
|
|
|
private let titleImageView: UIImageView = {
|
|
let view = UIImageView(image: UIImage(named: "Home/receive_message_title"))
|
|
view.contentMode = .scaleAspectFit
|
|
return view
|
|
}()
|
|
|
|
private let avatarView: UIImageView = {
|
|
let view = UIImageView()
|
|
view.contentMode = .scaleAspectFill
|
|
view.clipsToBounds = true
|
|
view.layer.cornerRadius = 16
|
|
view.layer.borderColor = UIColor.white.cgColor
|
|
view.layer.borderWidth = 4
|
|
return view
|
|
}()
|
|
|
|
private let nameLabel: UILabel = {
|
|
let label = UILabel()
|
|
label.font = .systemFont(ofSize: 15, weight: .semibold)
|
|
label.textColor = UIColor(hexStr: "#293445")
|
|
label.textAlignment = .center
|
|
return label
|
|
}()
|
|
|
|
private let relationLabel: UILabel = {
|
|
let label = UILabel()
|
|
label.text = "💕"
|
|
label.font = .systemFont(ofSize: 15)
|
|
return label
|
|
}()
|
|
|
|
private let messageImageView: UIImageView = {
|
|
let view = UIImageView()
|
|
view.backgroundColor = UIColor(hexStr: "#D9D9D9")
|
|
view.contentMode = .scaleAspectFill
|
|
view.clipsToBounds = true
|
|
view.layer.cornerRadius = 36
|
|
return view
|
|
}()
|
|
|
|
private let messageLabel: UILabel = {
|
|
let label = UILabel()
|
|
label.font = .systemFont(ofSize: 15, weight: .medium)
|
|
label.textColor = UIColor(hexStr: "#293445")
|
|
label.textAlignment = .center
|
|
label.numberOfLines = 0
|
|
label.lineBreakMode = .byTruncatingTail
|
|
return label
|
|
}()
|
|
|
|
private let burnButton: UIButton = {
|
|
let button = UIButton(type: .custom)
|
|
button.setTitle("阅后即焚", for: .normal)
|
|
button.setTitleColor(.white, for: .normal)
|
|
button.titleLabel?.font = FontManager.boboBold(18)
|
|
button.backgroundColor = UIColor(hexStr: "#293445")
|
|
button.layer.cornerRadius = 20
|
|
return button
|
|
}()
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
setupUI()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
private func setupUI() {
|
|
addSubview(overlayView)
|
|
addSubview(letterView)
|
|
overlayView.layoutChain.edges()
|
|
|
|
letterView.layoutChain
|
|
.edgesHorzontal(26)
|
|
.centerY()
|
|
|
|
letterView.addSubview(cardBackgroundView)
|
|
letterView.addSubview(titleImageView)
|
|
letterView.addSubview(avatarView)
|
|
letterView.addSubview(nameLabel)
|
|
|
|
letterView.addSubview(messageImageView)
|
|
letterView.addSubview(messageLabel)
|
|
letterView.addSubview(burnButton)
|
|
|
|
cardBackgroundView.layoutChain
|
|
.top(38)
|
|
.edgesHorzontal()
|
|
.heightToWidth(480/323)
|
|
.bottom()
|
|
|
|
titleImageView.layoutChain
|
|
.top()
|
|
.left(18)
|
|
.width(180)
|
|
.height(56)
|
|
|
|
avatarView.layoutChain
|
|
.topToBottomOfView(titleImageView, offset: 5)
|
|
.centerX()
|
|
.width(40)
|
|
.height(40)
|
|
|
|
nameLabel.layoutChain
|
|
.topToBottomOfView(avatarView, offset: 5)
|
|
.centerX()
|
|
|
|
messageImageView.layoutChain
|
|
.topToBottomOfView(nameLabel, offset: 16)
|
|
.centerX()
|
|
.width(200)
|
|
.height(224)
|
|
|
|
messageLabel.layoutChain
|
|
.topToBottomOfView(messageImageView, offset: 13)
|
|
.edgesHorzontal(30)
|
|
|
|
burnButton.layoutChain
|
|
.bottom(25)
|
|
.edgesHorzontal(24)
|
|
.height(56)
|
|
|
|
burnButton.addTarget(self, action: #selector(tapBurn), for: .touchUpInside)
|
|
}
|
|
|
|
private func configure(item: ReceiveMessageDisplayItem) {
|
|
nameLabel.text = item.senderName.isEmpty ? "圈子成员" : item.senderName
|
|
avatarView.image = item.avatar ?? UIImage(named: "UserIcon/1")
|
|
messageImageView.image = item.image
|
|
messageLabel.text = String(item.message.prefix(20))
|
|
}
|
|
|
|
private func animateInitialFlyIn() {
|
|
burnButton.isUserInteractionEnabled = false
|
|
letterView.alpha = 1
|
|
letterView.transform = .identity
|
|
letterView.layer.transform = CATransform3DIdentity
|
|
letterView.layer.shadowColor = UIColor.black.cgColor
|
|
letterView.layer.shadowRadius = 18
|
|
letterView.layer.shadowOffset = CGSize(width: 0, height: 12)
|
|
letterView.layer.shadowOpacity = 0
|
|
letterView.layer.shouldRasterize = true
|
|
letterView.layer.rasterizationScale = UIScreen.main.scale
|
|
|
|
func flightTransform(
|
|
x: CGFloat,
|
|
y: CGFloat,
|
|
z: CGFloat,
|
|
scale: CGFloat,
|
|
rotateZ: CGFloat,
|
|
rotateY: CGFloat
|
|
) -> CATransform3D {
|
|
var transform = CATransform3DIdentity
|
|
transform.m34 = -1 / 800
|
|
transform = CATransform3DTranslate(transform, x, y, z)
|
|
transform = CATransform3DRotate(transform, rotateY, 0, 1, 0)
|
|
transform = CATransform3DRotate(transform, rotateZ, 0, 0, 1)
|
|
return CATransform3DScale(transform, scale, scale, 1)
|
|
}
|
|
|
|
let rotationSteps = 16
|
|
let startX = min(bounds.width * 0.75, 280)
|
|
let startY = min(bounds.height * 0.48, 370)
|
|
let transformValues: [NSValue] = (0...rotationSteps).map { index in
|
|
guard index < rotationSteps else {
|
|
return NSValue(caTransform3D: CATransform3DIdentity)
|
|
}
|
|
|
|
let progress = CGFloat(index) / CGFloat(rotationSteps)
|
|
let remaining = 1 - progress
|
|
let distanceProgress = remaining * remaining
|
|
var x = -startX * distanceProgress
|
|
var y = -startY * distanceProgress
|
|
var z = -650 * distanceProgress
|
|
var scale = 0.88 + 0.12 * (1 - distanceProgress)
|
|
|
|
if index == rotationSteps - 1 {
|
|
x = -5
|
|
y = 4
|
|
z = 28
|
|
scale = 1.018
|
|
}
|
|
|
|
return NSValue(
|
|
caTransform3D: flightTransform(
|
|
x: x,
|
|
y: y,
|
|
z: z,
|
|
scale: scale,
|
|
rotateZ: -.pi * 8 * remaining,
|
|
rotateY: -.pi * 0.12 * remaining
|
|
)
|
|
)
|
|
}
|
|
let transformKeyTimes: [NSNumber] = (0...rotationSteps).map {
|
|
NSNumber(value: Double($0) / Double(rotationSteps))
|
|
}
|
|
|
|
let transformAnimation = CAKeyframeAnimation(keyPath: "transform")
|
|
transformAnimation.values = transformValues
|
|
transformAnimation.keyTimes = transformKeyTimes
|
|
transformAnimation.timingFunctions = Array(
|
|
repeating: CAMediaTimingFunction(name: .linear),
|
|
count: rotationSteps
|
|
)
|
|
|
|
let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity")
|
|
opacityAnimation.values = [0.35, 0.6, 0.82, 1, 1, 1]
|
|
opacityAnimation.keyTimes = [0, 0.22, 0.45, 0.68, 0.86, 1]
|
|
|
|
let shadowAnimation = CAKeyframeAnimation(keyPath: "shadowOpacity")
|
|
shadowAnimation.values = [0.02, 0.04, 0.1, 0.18, 0.3, 0]
|
|
shadowAnimation.keyTimes = [0, 0.22, 0.45, 0.68, 0.86, 1]
|
|
|
|
let group = CAAnimationGroup()
|
|
group.animations = [transformAnimation, opacityAnimation, shadowAnimation]
|
|
group.duration = 0.72
|
|
group.isRemovedOnCompletion = true
|
|
letterView.layer.add(group, forKey: "initialFlight")
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.74) { [weak self] in
|
|
guard let self, !self.isBurning else { return }
|
|
self.letterView.layer.shouldRasterize = false
|
|
self.letterView.layer.shadowOpacity = 0
|
|
self.letterView.layer.shadowRadius = 0
|
|
self.letterView.layer.shadowOffset = .zero
|
|
self.burnButton.isUserInteractionEnabled = true
|
|
}
|
|
}
|
|
|
|
@objc private func tapBurn() {
|
|
guard !isBurning else { return }
|
|
isBurning = true
|
|
burnButton.isUserInteractionEnabled = false
|
|
playBurnAnimation()
|
|
}
|
|
|
|
private func playBurnAnimation() {
|
|
layoutIfNeeded()
|
|
burnLetterFrame = letterView.convert(letterView.bounds, to: self)
|
|
|
|
let maskLayer = CAShapeLayer()
|
|
maskLayer.frame = letterView.bounds
|
|
maskLayer.fillColor = UIColor.white.cgColor
|
|
letterView.layer.mask = maskLayer
|
|
burnMaskLayer = maskLayer
|
|
|
|
addBurningEdges()
|
|
addBurnParticles()
|
|
|
|
burnStartTime = CACurrentMediaTime()
|
|
updateBurn(progress: 0, timestamp: burnStartTime)
|
|
|
|
let displayLink = CADisplayLink(target: self, selector: #selector(updateBurnFrame(_:)))
|
|
displayLink.add(to: .main, forMode: .common)
|
|
burnDisplayLink = displayLink
|
|
}
|
|
|
|
@objc private func updateBurnFrame(_ displayLink: CADisplayLink) {
|
|
let elapsed = max(0, displayLink.timestamp - burnStartTime)
|
|
let progress = min(1, elapsed / burnDuration)
|
|
updateBurn(progress: CGFloat(progress), timestamp: displayLink.timestamp)
|
|
|
|
guard progress >= 1 else { return }
|
|
displayLink.invalidate()
|
|
burnDisplayLink = nil
|
|
burnEmitterLayer?.birthRate = 0
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.16) { [weak self] in
|
|
self?.finishBurn()
|
|
}
|
|
}
|
|
|
|
private func updateBurn(progress: CGFloat, timestamp: CFTimeInterval) {
|
|
let localPoints = burnBoundaryPoints(progress: progress, timestamp: timestamp)
|
|
let maskPath = UIBezierPath()
|
|
maskPath.move(to: .zero)
|
|
maskPath.addLine(to: CGPoint(x: letterView.bounds.width, y: 0))
|
|
for point in localPoints.reversed() {
|
|
maskPath.addLine(to: point)
|
|
}
|
|
maskPath.close()
|
|
|
|
let edgePath = UIBezierPath()
|
|
for (index, point) in localPoints.enumerated() {
|
|
let converted = CGPoint(
|
|
x: burnLetterFrame.minX + point.x,
|
|
y: burnLetterFrame.minY + point.y
|
|
)
|
|
if index == 0 {
|
|
edgePath.move(to: converted)
|
|
} else {
|
|
edgePath.addLine(to: converted)
|
|
}
|
|
}
|
|
|
|
let averageY = localPoints.reduce(CGFloat.zero) { $0 + $1.y }
|
|
/ CGFloat(max(localPoints.count, 1))
|
|
let edgeOpacity = Float(min(1, max(0, (1 - progress) * 5)))
|
|
|
|
CATransaction.begin()
|
|
CATransaction.setDisableActions(true)
|
|
burnMaskLayer?.path = maskPath.cgPath
|
|
charEdgeLayer?.path = edgePath.cgPath
|
|
fireEdgeLayer?.path = edgePath.cgPath
|
|
hotEdgeLayer?.path = edgePath.cgPath
|
|
charEdgeLayer?.opacity = edgeOpacity
|
|
fireEdgeLayer?.opacity = edgeOpacity
|
|
hotEdgeLayer?.opacity = edgeOpacity
|
|
burnEmitterLayer?.emitterPosition = CGPoint(
|
|
x: burnLetterFrame.midX,
|
|
y: burnLetterFrame.minY + averageY
|
|
)
|
|
CATransaction.commit()
|
|
}
|
|
|
|
private func burnBoundaryPoints(progress: CGFloat, timestamp: CFTimeInterval) -> [CGPoint] {
|
|
let width = letterView.bounds.width
|
|
let height = letterView.bounds.height
|
|
if progress >= 0.995 {
|
|
return stride(from: CGFloat.zero, through: width, by: 9).map {
|
|
CGPoint(x: $0, y: 0)
|
|
}
|
|
}
|
|
|
|
let baseY = height * (1 - progress)
|
|
let phase = CGFloat(timestamp) * 13
|
|
var points: [CGPoint] = []
|
|
var x: CGFloat = 0
|
|
while x < width {
|
|
let wideWave = sin(x * 0.052 + phase) * 6
|
|
let smallWave = sin(x * 0.17 - phase * 1.4) * 2.8
|
|
let flicker = sin((x + phase * 18) * 0.31) * 1.8
|
|
let y = min(height, max(0, baseY + wideWave + smallWave + flicker))
|
|
points.append(CGPoint(x: x, y: y))
|
|
x += 9
|
|
}
|
|
points.append(CGPoint(x: width, y: min(height, max(0, baseY))))
|
|
return points
|
|
}
|
|
|
|
private func addBurningEdges() {
|
|
let charLayer = makeEdgeLayer(
|
|
color: UIColor(hexStr: "#2A1710", alpha: 0.8),
|
|
width: 10
|
|
)
|
|
let fireLayer = makeEdgeLayer(
|
|
color: UIColor(hexStr: "#FF5A20"),
|
|
width: 5.5
|
|
)
|
|
fireLayer.shadowColor = UIColor(hexStr: "#FF4B16").cgColor
|
|
fireLayer.shadowOpacity = 0.85
|
|
fireLayer.shadowRadius = 7
|
|
fireLayer.shadowOffset = .zero
|
|
|
|
let hotLayer = makeEdgeLayer(
|
|
color: UIColor(hexStr: "#FFD75A"),
|
|
width: 1.8
|
|
)
|
|
|
|
layer.addSublayer(charLayer)
|
|
layer.addSublayer(fireLayer)
|
|
layer.addSublayer(hotLayer)
|
|
effectLayers.append(contentsOf: [charLayer, fireLayer, hotLayer])
|
|
charEdgeLayer = charLayer
|
|
fireEdgeLayer = fireLayer
|
|
hotEdgeLayer = hotLayer
|
|
}
|
|
|
|
private func makeEdgeLayer(color: UIColor, width: CGFloat) -> CAShapeLayer {
|
|
let shapeLayer = CAShapeLayer()
|
|
shapeLayer.frame = bounds
|
|
shapeLayer.fillColor = UIColor.clear.cgColor
|
|
shapeLayer.strokeColor = color.cgColor
|
|
shapeLayer.lineWidth = width
|
|
shapeLayer.lineCap = .round
|
|
shapeLayer.lineJoin = .round
|
|
return shapeLayer
|
|
}
|
|
|
|
private func addBurnParticles() {
|
|
let emitter = CAEmitterLayer()
|
|
emitter.frame = bounds
|
|
emitter.emitterShape = .line
|
|
emitter.emitterMode = .surface
|
|
emitter.emitterSize = CGSize(width: burnLetterFrame.width - 40, height: 1)
|
|
emitter.emitterPosition = CGPoint(x: burnLetterFrame.midX, y: burnLetterFrame.maxY)
|
|
|
|
let ember = CAEmitterCell()
|
|
ember.contents = makeEmberImage()
|
|
ember.birthRate = 38
|
|
ember.lifetime = 0.58
|
|
ember.lifetimeRange = 0.18
|
|
ember.velocity = 62
|
|
ember.velocityRange = 34
|
|
ember.emissionLongitude = -.pi / 2
|
|
ember.emissionRange = .pi / 2.8
|
|
ember.scale = 0.65
|
|
ember.scaleRange = 0.3
|
|
ember.spinRange = .pi * 2
|
|
ember.alphaSpeed = -1.5
|
|
ember.yAcceleration = -22
|
|
|
|
let ash = CAEmitterCell()
|
|
ash.contents = makeAshImage()
|
|
ash.birthRate = 22
|
|
ash.lifetime = 0.8
|
|
ash.lifetimeRange = 0.2
|
|
ash.velocity = 38
|
|
ash.velocityRange = 20
|
|
ash.emissionLongitude = -.pi / 2
|
|
ash.emissionRange = .pi / 2
|
|
ash.scale = 0.7
|
|
ash.scaleRange = 0.3
|
|
ash.spin = 1.4
|
|
ash.spinRange = .pi * 2
|
|
ash.alphaSpeed = -1
|
|
|
|
let smoke = CAEmitterCell()
|
|
smoke.contents = makeParticleImage(
|
|
color: UIColor(hexStr: "#696969", alpha: 0.22),
|
|
size: CGSize(width: 11, height: 11)
|
|
)
|
|
smoke.birthRate = 8
|
|
smoke.lifetime = 0.9
|
|
smoke.lifetimeRange = 0.25
|
|
smoke.velocity = 30
|
|
smoke.velocityRange = 16
|
|
smoke.emissionLongitude = -.pi / 2
|
|
smoke.emissionRange = .pi / 3
|
|
smoke.scale = 0.55
|
|
smoke.scaleRange = 0.2
|
|
smoke.scaleSpeed = 0.12
|
|
smoke.alphaSpeed = -0.7
|
|
smoke.yAcceleration = -8
|
|
|
|
emitter.emitterCells = [ember, ash, smoke]
|
|
layer.addSublayer(emitter)
|
|
effectLayers.append(emitter)
|
|
burnEmitterLayer = emitter
|
|
}
|
|
|
|
private func makeParticleImage(color: UIColor, size: CGSize) -> CGImage? {
|
|
UIGraphicsImageRenderer(size: size).image { context in
|
|
context.cgContext.setFillColor(color.cgColor)
|
|
context.cgContext.fillEllipse(in: CGRect(origin: .zero, size: size))
|
|
}.cgImage
|
|
}
|
|
|
|
private func makeEmberImage() -> CGImage? {
|
|
let size = CGSize(width: 3, height: 8)
|
|
return UIGraphicsImageRenderer(size: size).image { _ in
|
|
UIColor(hexStr: "#FF982A").setFill()
|
|
UIBezierPath(
|
|
roundedRect: CGRect(origin: .zero, size: size),
|
|
cornerRadius: 1.5
|
|
).fill()
|
|
}.cgImage
|
|
}
|
|
|
|
private func makeAshImage() -> CGImage? {
|
|
let size = CGSize(width: 5, height: 3)
|
|
return UIGraphicsImageRenderer(size: size).image { _ in
|
|
UIColor(hexStr: "#4B403A", alpha: 0.72).setFill()
|
|
let path = UIBezierPath()
|
|
path.move(to: CGPoint(x: 0, y: 2.4))
|
|
path.addLine(to: CGPoint(x: 1.3, y: 0))
|
|
path.addLine(to: CGPoint(x: 5, y: 1.1))
|
|
path.addLine(to: CGPoint(x: 3.8, y: 3))
|
|
path.close()
|
|
path.fill()
|
|
}.cgImage
|
|
}
|
|
|
|
private func finishBurn() {
|
|
guard isBurning, let item else { return }
|
|
let completion = onBurn
|
|
if !messages.isEmpty {
|
|
messages.removeFirst()
|
|
}
|
|
|
|
guard let nextItem = messages.first else {
|
|
dismiss(animated: false)
|
|
completion?(item)
|
|
return
|
|
}
|
|
|
|
resetBurnState()
|
|
self.item = nextItem
|
|
configure(item: nextItem)
|
|
letterView.alpha = 0
|
|
letterView.transform = CGAffineTransform(scaleX: 0.94, y: 0.94)
|
|
UIView.animate(withDuration: 0.22, delay: 0, options: [.curveEaseOut]) {
|
|
self.letterView.alpha = 1
|
|
self.letterView.transform = .identity
|
|
}
|
|
completion?(item)
|
|
}
|
|
|
|
private func dismiss(animated: Bool) {
|
|
guard superview != nil else { return }
|
|
|
|
let completion = { [weak self] in
|
|
guard let self else { return }
|
|
self.removeFromSuperview()
|
|
self.resetBurnState()
|
|
self.messages.removeAll()
|
|
self.item = nil
|
|
self.onBurn = nil
|
|
}
|
|
|
|
guard animated else {
|
|
completion()
|
|
return
|
|
}
|
|
|
|
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseIn]) {
|
|
self.overlayView.alpha = 0
|
|
self.letterView.alpha = 0
|
|
self.letterView.transform = CGAffineTransform(scaleX: 0.94, y: 0.94)
|
|
} completion: { _ in
|
|
completion()
|
|
}
|
|
}
|
|
|
|
private func resetBurnState() {
|
|
burnDisplayLink?.invalidate()
|
|
burnDisplayLink = nil
|
|
isBurning = false
|
|
burnButton.isUserInteractionEnabled = true
|
|
letterView.alpha = 1
|
|
letterView.transform = .identity
|
|
letterView.layer.transform = CATransform3DIdentity
|
|
letterView.layer.mask = nil
|
|
letterView.layer.removeAllAnimations()
|
|
letterView.layer.shouldRasterize = false
|
|
letterView.layer.shadowOpacity = 0
|
|
letterView.layer.shadowRadius = 0
|
|
letterView.layer.shadowOffset = .zero
|
|
for layer in effectLayers {
|
|
layer.removeAllAnimations()
|
|
layer.removeFromSuperlayer()
|
|
}
|
|
effectLayers.removeAll()
|
|
burnMaskLayer = nil
|
|
charEdgeLayer = nil
|
|
fireEdgeLayer = nil
|
|
hotEdgeLayer = nil
|
|
burnEmitterLayer = nil
|
|
burnLetterFrame = .zero
|
|
}
|
|
}
|