Let’s look at the example first.
The speed of each barrage is different, and the barrage as a whole is supported to start and pause.
If there are too many bullet screens, there is a buffer queue to keep retrying whether it can be displayed, so as to ensure that all the text can be displayed, and each item can be displayed.
The implementation is based on CADisplayLink, so it is smoother than directly setting up a timer to calculate the offset. The simple translation animation is as follows:
import UIKit
class ViewController: UIViewController {
let squareView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// 创建 CADisplayLink 对象
let displayLink = CADisplayLink(target: self, selector: #selector(update))
// 将视图控制器添加到 displayLink 中
displayLink.add(self, for: .common)
// 设置视图属性
squareView.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
squareView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
view.addSubview(squareView)
}
@objc func update(_ displayLink: CADisplayLink) {
// 在每一帧更新时移动视图
squareView.frame.origin.x += 5
}
}
A slight modification on this basic version becomes the following code:
import Foundation
import UIKit
class XDanMu {
var row: Int = 0
var label: UILabel = UILabel()
var speed: CGFloat = 0
var isMe: Bool = false
}
class XDanMuView: UIView {
var displayLink: CADisplayLink?
var lineHeight: CGFloat = 26
var gap: CGFloat = 20
var minSpeed: CGFloat = 1
var maxSpeed: CGFloat = 2
var isPause: Bool = false
var danmus: [XDanMu] = []
var danmuQueue: [(String, Bool)] = []
var timer: Timer?
func start() {
displayLink = CADisplayLink(target: self, selector: #selector(update))
displayLink?.add(to: RunLoop.current, forMode: .common)
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleDanMuQueue), userInfo: nil, repeats: true)
}
@objc func handleDanMuQueue() {
if danmuQueue.isEmpty {
return
}
let danmu = danmuQueue.removeFirst()
addDanMu(text: danmu.0, isMe: danmu.1)
}
@objc func addDanMu(text: String, isMe: Bool) {
let danmu = XDanMu()
danmu.label.frame.origin.x = self.frame.size.width
danmu.label.text = text
danmu.label.sizeToFit()
if isMe {
danmu.label.layer.borderWidth = 1
}
var linelasts: [XDanMu?] = []
let rows: Int = Int(self.frame.size.height / lineHeight)
for _ in 0..<rows {
linelasts.append(nil)
}
for d in danmus {
if d.row >= linelasts.count {
break
}
if linelasts[d.row] != nil {
let endx = danmu.label.frame.origin.x
let targetx = linelasts[d.row]!.label.frame.origin.x
if endx > targetx {
linelasts[d.row] = d
}
} else {
linelasts[d.row] = d
}
}
var isMatch = false
for index in 0..<linelasts.count {
if let d = linelasts[index] {
let endx = d.label.frame.origin.x + d.label.frame.size.width + gap
if endx < self.frame.size.width {
danmu.row = index
var ms = self.frame.size.width / endx * d.speed
ms = CGFloat.minimum(ms, maxSpeed)
danmu.speed = CGFloat.random(in: minSpeed...ms)
isMatch = true
break
}
} else {
danmu.row = index
danmu.speed = CGFloat.random(in: minSpeed...maxSpeed)
isMatch = true
break
}
}
if isMatch == false {
danmuQueue.append((text, isMe))
return
}
danmu.label.frame.origin.y = lineHeight * CGFloat(danmu.row)
self.addSubview(danmu.label)
self.danmus.append(danmu)
}
@objc func update(_ displayLink: CADisplayLink) {
if isPause == true {
return
}
// 在每一帧更新时移动视图
for index in 0..<danmus.count {
let danmu = danmus[index]
danmu.label.frame.origin.x -= danmu.speed
if danmu.label.frame.origin.x < -danmu.label.frame.size.width {
danmu.label.removeFromSuperview()
danmus.remove(at: index)
break
}
}
}
}
Then find a place that needs to be used and add the following code to achieve the effect of the above picture
override func viewDidLoad() {
super.viewDidLoad()
var danmuView: XDanMuView = XDanMuView()
danmuView.frame = .init(x: 0, y: 100, width: self.view.frame.size.width, height: self.view.frame.size.height - 200)
self.view.addSubview(danmuView)
// 配置项
danmuView.minSpeed = 1
danmuView.maxSpeed = 2
danmuView.gap = 20
danmuView.lineHeight = 30
// 启动弹幕
danmuView.start()
// 启动一个定时器灌弹幕
timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(addDanMu), userInfo: nil, repeats: false)
}
@objc func addDanMu() {
let interval = CGFloat.random(in: 0.3...1.0)
Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(addDanMu), userInfo: nil, repeats: false)
var text = ""
for _ in 0...Int.random(in: 1...30) {
text += "嘿"
}
for _ in 0...Int.random(in: 1...2) {
danmuView.addDanMu(text: text, isMe: Bool.random())
}
}
The font of the text is modified according to the needs. Currently, no styles and colors have been added.
Complete project portal
github
gitee