import UIKit private extension UILabel { func textWidth() -> CGFloat { return UILabel.textWidth(label: self) } class func textWidth(label: UILabel) -> CGFloat { return textWidth(label: label, text: label.text!) } class func textWidth(label: UILabel, text: String) -> CGFloat { return textWidth(font: label.font, text: text) } class func textWidth(font: UIFont, text: String) -> CGFloat { let myText = text as NSString let rect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil) return ceil(labelSize.width) } } open class RollingLabel: UILabel { private var fullText = "" private var suffix: String = "" open var showSymbol = false private var scrollLayers: [CAScrollLayer] = [] private var scrollLabels: [UILabel] = [] private let duration = 1.12 private let durationOffset = 0.2 private let textsNotAnimated = [","] public func text(num: Int) { self.configure(with: num) self.text = " " self.animate() } public func setPrefix(prefix: String) { self.suffix = prefix } private func configure(with number: Int) { fullText = String(number) clean() setupSubviews() } private func animate(ascending: Bool = true) { createAnimations(ascending: ascending) } private func clean() { self.text = nil self.subviews.forEach { $0.removeFromSuperview() } self.layer.sublayers?.forEach { $0.removeFromSuperlayer() } scrollLayers.removeAll() scrollLabels.removeAll() } private func setupSubviews() { let stringArray = fullText.map { String($0) } var x: CGFloat = 0 let y: CGFloat = 0 if self.textAlignment == .center { if showSymbol { self.text = "\(fullText) \(suffix)" } else { self.text = fullText } let w = UILabel.textWidth(font: self.font, text: self.text ?? "") self.text = "" x = -(w / 2) } else if self.textAlignment == .right { if showSymbol { self.text = "\(fullText) \(suffix) " } else { self.text = fullText } let w = UILabel.textWidth(font: self.font, text: self.text ?? "") self.text = "" x = -w } if showSymbol { let wLabel = UILabel() wLabel.frame.origin = CGPoint(x: x, y: y) wLabel.textColor = textColor wLabel.font = font wLabel.text = "\(suffix) " wLabel.textAlignment = .center wLabel.sizeToFit() self.addSubview(wLabel) x += wLabel.bounds.width } stringArray.enumerated().forEach { index, text in if textsNotAnimated.contains(text) { let label = UILabel() label.frame.origin = CGPoint(x: x, y: y) label.textColor = textColor label.font = font label.text = text label.textAlignment = .center label.sizeToFit() self.addSubview(label) x += label.bounds.width } else { let label = UILabel() label.frame.origin = CGPoint(x: x, y: y) label.textColor = textColor label.font = font label.text = "0" label.textAlignment = .center label.sizeToFit() createScrollLayer(to: label, text: text) x += label.bounds.width } } } private func createScrollLayer(to label: UILabel, text: String) { let scrollLayer = CAScrollLayer() scrollLayer.frame = label.frame scrollLayers.append(scrollLayer) self.layer.addSublayer(scrollLayer) createContentForLayer(scrollLayer: scrollLayer, text: text) } private func createContentForLayer(scrollLayer: CAScrollLayer, text: String) { var textsForScroll: [String] = [] let max: Int var found = false if let val = Int(text) { max = val found = true } else { max = 9 } for i in 0...max { let str = String(i) textsForScroll.append(str) } if !found { textsForScroll.append(text) } var height: CGFloat = 0 for text in textsForScroll { let label = UILabel() label.text = text label.textColor = textColor label.font = font label.textAlignment = .center label.frame = CGRect(x: 0, y: height, width: scrollLayer.frame.width, height: scrollLayer.frame.height) scrollLayer.addSublayer(label.layer) scrollLabels.append(label) height = label.frame.maxY } } private func createAnimations(ascending: Bool) { var offset: CFTimeInterval = 0.0 for scrollLayer in scrollLayers { let maxY = scrollLayer.sublayers?.last?.frame.origin.y ?? 0.0 let animation = CABasicAnimation(keyPath: "sublayerTransform.translation.y") animation.duration = duration + offset animation.timingFunction = CAMediaTimingFunction(name: .easeOut) if ascending { animation.fromValue = maxY animation.toValue = 0 } else { animation.fromValue = 0 animation.toValue = maxY } scrollLayer.scrollMode = .vertically scrollLayer.add(animation, forKey: nil) scrollLayer.scroll(to: CGPoint(x: 0, y: maxY)) offset += self.durationOffset } } }