150 lines
6.5 KiB
Swift

import Foundation
import UIKit
import Display
import AppBundle
import HierarchyTrackingLayer
private let shadowImage: UIImage? = {
UIImage(named: "Stories/PanelGradient")
}()
public final class TextLoadingEffectView: UIView {
let hierarchyTrackingLayer: HierarchyTrackingLayer
private let maskContentsView: UIView
private let maskHighlightNode: LinkHighlightingNode
private let maskBorderContentsView: UIView
private let maskBorderHighlightNode: LinkHighlightingNode
private let backgroundView: UIImageView
private let borderBackgroundView: UIImageView
private let duration: Double
private let gradientWidth: CGFloat
private var size: CGSize?
override public init(frame: CGRect) {
self.hierarchyTrackingLayer = HierarchyTrackingLayer()
self.maskContentsView = UIView()
self.maskHighlightNode = LinkHighlightingNode(color: .black)
//self.maskHighlightNode.useModernPathCalculation = true
self.maskBorderContentsView = UIView()
self.maskBorderHighlightNode = LinkHighlightingNode(color: .black)
self.maskBorderHighlightNode.borderOnly = true
//self.maskBorderHighlightNode.useModernPathCalculation = true
self.maskBorderContentsView.addSubview(self.maskBorderHighlightNode.view)
self.backgroundView = UIImageView()
self.borderBackgroundView = UIImageView()
self.gradientWidth = 120.0
self.duration = 1.0
super.init(frame: frame)
self.isUserInteractionEnabled = false
self.maskContentsView.mask = self.maskHighlightNode.view
self.maskContentsView.addSubview(self.backgroundView)
self.addSubview(self.maskContentsView)
self.maskBorderContentsView.mask = self.maskBorderHighlightNode.view
self.maskBorderContentsView.addSubview(self.borderBackgroundView)
self.addSubview(self.maskBorderContentsView)
let generateGradient: (CGFloat) -> UIImage? = { baseAlpha in
return generateImage(CGSize(width: self.gradientWidth, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let foregroundColor = UIColor(white: 1.0, alpha: min(1.0, baseAlpha * 4.0))
if let shadowImage {
UIGraphicsPushContext(context)
for i in 0 ..< 2 {
let shadowFrame = CGRect(origin: CGPoint(x: CGFloat(i) * (size.width * 0.5), y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height))
context.saveGState()
context.translateBy(x: shadowFrame.midX, y: shadowFrame.midY)
context.rotate(by: CGFloat(i == 0 ? 1.0 : -1.0) * CGFloat.pi * 0.5)
let adjustedRect = CGRect(origin: CGPoint(x: -shadowFrame.height * 0.5, y: -shadowFrame.width * 0.5), size: CGSize(width: shadowFrame.height, height: shadowFrame.width))
context.clip(to: adjustedRect, mask: shadowImage.cgImage!)
context.setFillColor(foregroundColor.cgColor)
context.fill(adjustedRect)
context.restoreGState()
}
UIGraphicsPopContext()
}
})?.withRenderingMode(.alwaysTemplate)
}
self.backgroundView.image = generateGradient(0.5)
self.borderBackgroundView.image = generateGradient(1.0)
self.layer.addSublayer(self.hierarchyTrackingLayer)
self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
guard let self, let size = self.size else {
return
}
self.updateAnimations(size: size)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func updateAnimations(size: CGSize) {
if self.backgroundView.layer.animation(forKey: "shimmer") != nil {
return
}
let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: (size.width + self.gradientWidth + size.width * 0.0) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: self.duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
animation.repeatCount = Float.infinity
self.backgroundView.layer.add(animation, forKey: "shimmer")
self.borderBackgroundView.layer.add(animation, forKey: "shimmer")
}
public func update(color: UIColor, textNode: TextNode, range: NSRange) {
var rectsSet: [CGRect] = []
if let cachedLayout = textNode.cachedLayout {
if let rects = cachedLayout.rangeRects(in: range)?.rects, !rects.isEmpty {
rectsSet = rects
}
}
let maskFrame = CGRect(origin: CGPoint(), size: textNode.bounds.size).insetBy(dx: -4.0, dy: -4.0)
self.maskContentsView.backgroundColor = color.withAlphaComponent(0.1)
self.maskBorderContentsView.backgroundColor = color.withAlphaComponent(0.12)
self.backgroundView.tintColor = color
self.borderBackgroundView.tintColor = color
self.maskContentsView.frame = maskFrame
self.maskBorderContentsView.frame = maskFrame
self.maskHighlightNode.updateRects(rectsSet)
self.maskHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize())
self.maskBorderHighlightNode.updateRects(rectsSet)
self.maskBorderHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize())
if self.size != maskFrame.size {
self.size = maskFrame.size
self.backgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height))
self.borderBackgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height))
self.updateAnimations(size: maskFrame.size)
}
}
}