mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
581 lines
26 KiB
Swift
581 lines
26 KiB
Swift
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import HierarchyTrackingLayer
|
|
|
|
public final class ShimmerEffectForegroundView: UIView {
|
|
private var currentBackgroundColor: UIColor?
|
|
private var currentForegroundColor: UIColor?
|
|
private var currentHorizontal: Bool?
|
|
private var currentGradientSize: CGFloat?
|
|
private var currentDuration: Double?
|
|
private let imageContainer: SimpleLayer
|
|
private let image: SimpleLayer
|
|
|
|
private var absoluteLocation: (CGRect, CGSize)?
|
|
private var isCurrentlyInHierarchy = false
|
|
private var shouldBeAnimating = false
|
|
private var globalTimeOffset = true
|
|
|
|
private let trackingLayer: HierarchyTrackingLayer
|
|
|
|
public init() {
|
|
self.imageContainer = SimpleLayer()
|
|
|
|
self.image = SimpleLayer()
|
|
self.image.contentsGravity = .resizeAspectFill
|
|
|
|
self.trackingLayer = HierarchyTrackingLayer()
|
|
|
|
super.init(frame: CGRect())
|
|
|
|
self.clipsToBounds = true
|
|
self.isUserInteractionEnabled = false
|
|
|
|
self.layer.addSublayer(self.imageContainer)
|
|
self.imageContainer.addSublayer(self.image)
|
|
|
|
self.layer.addSublayer(self.trackingLayer)
|
|
|
|
self.trackingLayer.didEnterHierarchy = { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.isCurrentlyInHierarchy = true
|
|
strongSelf.updateAnimation()
|
|
}
|
|
|
|
self.trackingLayer.didExitHierarchy = { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.isCurrentlyInHierarchy = false
|
|
strongSelf.updateAnimation()
|
|
}
|
|
}
|
|
|
|
required public init(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
public func update(backgroundColor: UIColor, foregroundColor: UIColor, gradientSize: CGFloat?, globalTimeOffset: Bool, duration: Double?, horizontal: Bool = false) {
|
|
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor), self.currentHorizontal == horizontal, self.currentGradientSize == gradientSize {
|
|
return
|
|
}
|
|
self.currentBackgroundColor = backgroundColor
|
|
self.currentForegroundColor = foregroundColor
|
|
self.currentHorizontal = horizontal
|
|
self.currentGradientSize = gradientSize
|
|
self.globalTimeOffset = globalTimeOffset
|
|
self.currentDuration = duration
|
|
|
|
let image: UIImage?
|
|
if horizontal {
|
|
image = generateImage(CGSize(width: gradientSize ?? 320.0, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setFillColor(backgroundColor.cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
|
|
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
|
|
|
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
|
let peakColor = foregroundColor.cgColor
|
|
|
|
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
|
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
|
|
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
|
|
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
|
|
})
|
|
} else {
|
|
image = generateImage(CGSize(width: 16.0, height: gradientSize ?? 250.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setFillColor(backgroundColor.cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
|
|
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
|
|
|
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
|
let peakColor = foregroundColor.cgColor
|
|
|
|
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
|
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
|
|
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
|
|
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
|
})
|
|
}
|
|
self.image.contents = image?.cgImage
|
|
self.updateAnimation()
|
|
}
|
|
|
|
public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
|
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
|
return
|
|
}
|
|
let sizeUpdated = self.absoluteLocation?.1 != containerSize
|
|
let frameUpdated = self.absoluteLocation?.0 != rect
|
|
self.absoluteLocation = (rect, containerSize)
|
|
|
|
if sizeUpdated {
|
|
if self.shouldBeAnimating {
|
|
self.image.removeAnimation(forKey: "shimmer")
|
|
self.addImageAnimation()
|
|
} else {
|
|
self.updateAnimation()
|
|
}
|
|
}
|
|
|
|
if frameUpdated {
|
|
self.imageContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
|
}
|
|
}
|
|
|
|
private func updateAnimation() {
|
|
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil && self.currentHorizontal != nil
|
|
if shouldBeAnimating != self.shouldBeAnimating {
|
|
self.shouldBeAnimating = shouldBeAnimating
|
|
if shouldBeAnimating {
|
|
self.addImageAnimation()
|
|
} else {
|
|
self.image.removeAnimation(forKey: "shimmer")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func addImageAnimation() {
|
|
guard let containerSize = self.absoluteLocation?.1, let horizontal = self.currentHorizontal else {
|
|
return
|
|
}
|
|
|
|
if horizontal {
|
|
let gradientSize = self.currentGradientSize ?? 320.0
|
|
self.image.frame = CGRect(origin: CGPoint(x: -gradientSize, y: 0.0), size: CGSize(width: gradientSize, height: containerSize.height))
|
|
let animation = self.image.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.width + gradientSize) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: self.currentDuration ?? 1.3, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
|
animation.repeatCount = Float.infinity
|
|
if self.globalTimeOffset {
|
|
animation.beginTime = 1.0
|
|
}
|
|
self.image.add(animation, forKey: "shimmer")
|
|
} else {
|
|
let gradientSize = self.currentGradientSize ?? 250.0
|
|
self.image.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientSize), size: CGSize(width: containerSize.width, height: gradientSize))
|
|
let animation = self.image.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientSize) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: self.currentDuration ?? 1.3, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
|
animation.repeatCount = Float.infinity
|
|
if self.globalTimeOffset {
|
|
animation.beginTime = 1.0
|
|
}
|
|
self.image.add(animation, forKey: "shimmer")
|
|
}
|
|
}
|
|
}
|
|
|
|
private let shadowImage: UIImage? = {
|
|
UIImage(named: "Stories/PanelGradient")
|
|
}()
|
|
|
|
public final class ShimmerEffectForegroundNode: ASDisplayNode {
|
|
private var currentBackgroundColor: UIColor?
|
|
private var currentForegroundColor: UIColor?
|
|
private var currentHorizontal: Bool?
|
|
private let imageNodeContainer: ASDisplayNode
|
|
private let imageNode: ASImageNode
|
|
|
|
private var absoluteLocation: (CGRect, CGSize)?
|
|
private var isCurrentlyInHierarchy = false
|
|
private var shouldBeAnimating = false
|
|
private var globalTimeOffset = true
|
|
private var duration: Double?
|
|
|
|
public override init() {
|
|
self.imageNodeContainer = ASDisplayNode()
|
|
self.imageNodeContainer.isLayerBacked = true
|
|
|
|
self.imageNode = ASImageNode()
|
|
self.imageNode.isLayerBacked = true
|
|
self.imageNode.displaysAsynchronously = false
|
|
self.imageNode.displayWithoutProcessing = true
|
|
self.imageNode.contentMode = .scaleToFill
|
|
|
|
super.init()
|
|
|
|
self.isLayerBacked = true
|
|
self.clipsToBounds = true
|
|
|
|
self.imageNodeContainer.addSubnode(self.imageNode)
|
|
self.addSubnode(self.imageNodeContainer)
|
|
}
|
|
|
|
public override func didEnterHierarchy() {
|
|
super.didEnterHierarchy()
|
|
|
|
self.isCurrentlyInHierarchy = true
|
|
self.updateAnimation()
|
|
}
|
|
|
|
public override func didExitHierarchy() {
|
|
super.didExitHierarchy()
|
|
|
|
self.isCurrentlyInHierarchy = false
|
|
self.updateAnimation()
|
|
}
|
|
|
|
public func update(backgroundColor: UIColor, foregroundColor: UIColor, horizontal: Bool, effectSize: CGFloat?, globalTimeOffset: Bool, duration: Double?) {
|
|
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor), self.currentHorizontal == horizontal {
|
|
return
|
|
}
|
|
self.currentBackgroundColor = backgroundColor
|
|
self.currentForegroundColor = foregroundColor
|
|
self.currentHorizontal = horizontal
|
|
self.globalTimeOffset = globalTimeOffset
|
|
self.duration = duration
|
|
|
|
let image: UIImage?
|
|
if horizontal {
|
|
let baseAlpha: CGFloat = 0.1
|
|
image = generateImage(CGSize(width: effectSize ?? 200.0, 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()
|
|
}
|
|
})
|
|
} else {
|
|
image = generateImage(CGSize(width: 16.0, height: 320.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setFillColor(backgroundColor.cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
|
|
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
|
|
|
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
|
let peakColor = foregroundColor.cgColor
|
|
|
|
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
|
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
|
|
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
|
|
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
|
})
|
|
}
|
|
self.imageNode.image = image
|
|
self.updateAnimation()
|
|
}
|
|
|
|
public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
|
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
|
return
|
|
}
|
|
let sizeUpdated = self.absoluteLocation?.1 != containerSize
|
|
let frameUpdated = self.absoluteLocation?.0 != rect
|
|
self.absoluteLocation = (rect, containerSize)
|
|
|
|
if sizeUpdated {
|
|
if self.shouldBeAnimating {
|
|
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
|
self.addImageAnimation()
|
|
} else {
|
|
self.updateAnimation()
|
|
}
|
|
}
|
|
|
|
if frameUpdated {
|
|
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
|
}
|
|
}
|
|
|
|
private func updateAnimation() {
|
|
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil && self.currentHorizontal != nil
|
|
if shouldBeAnimating != self.shouldBeAnimating {
|
|
self.shouldBeAnimating = shouldBeAnimating
|
|
if shouldBeAnimating {
|
|
self.addImageAnimation()
|
|
} else {
|
|
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func addImageAnimation() {
|
|
guard let containerSize = self.absoluteLocation?.1, let horizontal = self.currentHorizontal else {
|
|
return
|
|
}
|
|
|
|
if horizontal {
|
|
let gradientHeight: CGFloat = self.imageNode.image?.size.width ?? 320.0
|
|
self.imageNode.frame = CGRect(origin: CGPoint(x: -gradientHeight, y: 0.0), size: CGSize(width: gradientHeight, height: containerSize.height))
|
|
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.width + gradientHeight) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: duration ?? 1.3, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
|
animation.repeatCount = Float.infinity
|
|
if self.globalTimeOffset {
|
|
animation.beginTime = 1.0
|
|
}
|
|
self.imageNode.layer.add(animation, forKey: "shimmer")
|
|
} else {
|
|
let gradientHeight: CGFloat = 250.0
|
|
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight))
|
|
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: duration ?? 1.3, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
|
animation.repeatCount = Float.infinity
|
|
if self.globalTimeOffset {
|
|
animation.beginTime = 1.0
|
|
}
|
|
self.imageNode.layer.add(animation, forKey: "shimmer")
|
|
}
|
|
}
|
|
}
|
|
|
|
public final class ShimmerEffectNode: ASDisplayNode {
|
|
public enum Shape: Equatable {
|
|
case circle(CGRect)
|
|
case roundedRectLine(startPoint: CGPoint, width: CGFloat, diameter: CGFloat)
|
|
case roundedRect(rect: CGRect, cornerRadius: CGFloat)
|
|
case rect(rect: CGRect)
|
|
case image(image: UIImage, rect: CGRect)
|
|
}
|
|
|
|
private let backgroundNode: ASDisplayNode
|
|
private let effectNode: ShimmerEffectForegroundNode
|
|
private let foregroundNode: ASImageNode
|
|
|
|
private var currentShapes: [Shape] = []
|
|
private var currentBackgroundColor: UIColor?
|
|
private var currentForegroundColor: UIColor?
|
|
private var currentShimmeringColor: UIColor?
|
|
private var currentHorizontal: Bool?
|
|
private var currentEffectSize: CGFloat?
|
|
private var currentSize = CGSize()
|
|
|
|
override public init() {
|
|
self.backgroundNode = ASDisplayNode()
|
|
|
|
self.effectNode = ShimmerEffectForegroundNode()
|
|
|
|
self.foregroundNode = ASImageNode()
|
|
self.foregroundNode.displaysAsynchronously = false
|
|
self.foregroundNode.displayWithoutProcessing = true
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.backgroundNode)
|
|
self.addSubnode(self.effectNode)
|
|
self.addSubnode(self.foregroundNode)
|
|
}
|
|
|
|
public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
|
self.effectNode.updateAbsoluteRect(rect, within: containerSize)
|
|
}
|
|
|
|
public func update(backgroundColor: UIColor, foregroundColor: UIColor, shimmeringColor: UIColor, shapes: [Shape], horizontal: Bool = false, effectSize: CGFloat? = nil, globalTimeOffset: Bool = true, duration: Double? = nil, size: CGSize, mask: Bool = false) {
|
|
if self.currentShapes == shapes, let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor), let currentShimmeringColor = self.currentShimmeringColor, currentShimmeringColor.isEqual(shimmeringColor), horizontal == self.currentHorizontal, effectSize == self.currentEffectSize, self.currentSize == size {
|
|
return
|
|
}
|
|
|
|
self.currentBackgroundColor = backgroundColor
|
|
self.currentForegroundColor = foregroundColor
|
|
self.currentShimmeringColor = shimmeringColor
|
|
self.currentShapes = shapes
|
|
self.currentHorizontal = horizontal
|
|
self.currentSize = size
|
|
|
|
self.backgroundNode.backgroundColor = foregroundColor
|
|
|
|
self.effectNode.update(backgroundColor: foregroundColor, foregroundColor: shimmeringColor, horizontal: horizontal, effectSize: effectSize, globalTimeOffset: globalTimeOffset, duration: duration)
|
|
|
|
self.foregroundNode.image = generateImage(size, rotatedContext: { size, context in
|
|
if !mask {
|
|
context.setFillColor(backgroundColor.cgColor)
|
|
context.setBlendMode(.copy)
|
|
context.fill(CGRect(origin: .zero, size: size))
|
|
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
} else {
|
|
context.clear(CGRect(origin: .zero, size: size))
|
|
|
|
context.setFillColor(UIColor.white.cgColor)
|
|
}
|
|
for shape in shapes {
|
|
switch shape {
|
|
case let .circle(frame):
|
|
context.fillEllipse(in: frame)
|
|
case let .roundedRectLine(startPoint, width, diameter):
|
|
context.fillEllipse(in: CGRect(origin: startPoint, size: CGSize(width: diameter, height: diameter)))
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: startPoint.x + width - diameter, y: startPoint.y), size: CGSize(width: diameter, height: diameter)))
|
|
context.fill(CGRect(origin: CGPoint(x: startPoint.x + diameter / 2.0, y: startPoint.y), size: CGSize(width: width - diameter, height: diameter)))
|
|
case let .roundedRect(rect, radius):
|
|
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: [.topLeft, .topRight, .bottomLeft, .bottomRight], cornerRadii: CGSize(width: radius, height: radius))
|
|
UIGraphicsPushContext(context)
|
|
path.fill()
|
|
UIGraphicsPopContext()
|
|
case let .rect(rect):
|
|
context.fill(rect)
|
|
case let .image(image, rect):
|
|
if let image = image.cgImage {
|
|
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
|
context.scaleBy(x: 1.0, y: -1.0)
|
|
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
|
context.clip(to: rect, mask: image)
|
|
context.fill(rect)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
if mask {
|
|
if self.view.mask == nil {
|
|
self.foregroundNode.removeFromSupernode()
|
|
self.view.mask = self.foregroundNode.view
|
|
}
|
|
} else {
|
|
if self.view.mask != nil {
|
|
self.view.mask = nil
|
|
if self.foregroundNode.supernode == nil {
|
|
self.addSubnode(self.foregroundNode)
|
|
}
|
|
}
|
|
}
|
|
|
|
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
self.foregroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
self.effectNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
}
|
|
}
|
|
|
|
public final class StandaloneShimmerEffect {
|
|
private var image: UIImage?
|
|
|
|
private var background: UIColor?
|
|
private var foreground: UIColor?
|
|
|
|
public var layer: CALayer? {
|
|
didSet {
|
|
if self.layer !== oldValue {
|
|
self.updateLayer()
|
|
}
|
|
}
|
|
}
|
|
|
|
public init() {
|
|
}
|
|
|
|
public func update(background: UIColor, foreground: UIColor) {
|
|
if self.background == background && self.foreground == foreground {
|
|
return
|
|
}
|
|
self.background = background
|
|
self.foreground = foreground
|
|
|
|
self.image = generateImage(CGSize(width: 1.0, height: 320.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setFillColor(background.cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
|
|
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
|
|
|
let transparentColor = foreground.withAlphaComponent(0.0).cgColor
|
|
let peakColor = foreground.cgColor
|
|
|
|
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
|
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
|
|
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
|
|
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
|
})
|
|
|
|
self.updateLayer()
|
|
}
|
|
|
|
public func updateHorizontal(background: UIColor, foreground: UIColor) {
|
|
if self.background == background && self.foreground == foreground {
|
|
return
|
|
}
|
|
self.background = background
|
|
self.foreground = foreground
|
|
|
|
self.image = generateImage(CGSize(width: 320, height: 1), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setFillColor(background.cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
|
|
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
|
|
|
let transparentColor = foreground.withAlphaComponent(0.0).cgColor
|
|
let peakColor = foreground.cgColor
|
|
|
|
var locations: [CGFloat] = [0.0, 0.44, 0.55, 1.0]
|
|
let colors: [CGColor] = [transparentColor, peakColor, peakColor, transparentColor]
|
|
|
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
guard let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations) else { return }
|
|
|
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.2), end: CGPoint(x: size.width, y: 0.8), options: CGGradientDrawingOptions())
|
|
})
|
|
|
|
self.updateHorizontalLayer()
|
|
}
|
|
|
|
public func updateLayer() {
|
|
guard let layer = self.layer, let image = self.image else {
|
|
return
|
|
}
|
|
|
|
layer.contents = image.cgImage
|
|
|
|
if layer.animation(forKey: "shimmer") == nil {
|
|
let animation = CABasicAnimation(keyPath: "contentsRect.origin.y")
|
|
animation.fromValue = 1.0 as NSNumber
|
|
animation.toValue = -1.0 as NSNumber
|
|
animation.isAdditive = true
|
|
animation.repeatCount = .infinity
|
|
animation.duration = 0.8
|
|
animation.beginTime = layer.convertTime(1.0, from: nil)
|
|
layer.add(animation, forKey: "shimmer")
|
|
}
|
|
}
|
|
|
|
private func updateHorizontalLayer() {
|
|
guard let layer = self.layer, let image = self.image else {
|
|
return
|
|
}
|
|
|
|
layer.contents = image.cgImage
|
|
|
|
if layer.animation(forKey: "shimmer") == nil {
|
|
var delay: TimeInterval { 1.6 }
|
|
let animation = CABasicAnimation(keyPath: "contentsRect.origin.x")
|
|
animation.fromValue = NSNumber(floatLiteral: delay)
|
|
animation.toValue = NSNumber(floatLiteral: -delay)
|
|
animation.isAdditive = true
|
|
animation.repeatCount = .infinity
|
|
animation.duration = 0.8 * delay
|
|
animation.timingFunction = .init(name: .easeInEaseOut)
|
|
layer.add(animation, forKey: "shimmer")
|
|
}
|
|
}
|
|
}
|