DanmakuKit

repository·master·Indexed 42 days ago

https://github.com/qyz777/danmakukit

A high-performance Swift library for implementing danmaku (bullet chat) functionality in iOS applications. It supports floating, top, and bottom launch types, speed and track adjustments, and both synchronous and asynchronous drawing. The library utilizes a decoupled architecture with DanmakuCellModel for data and DanmakuCell for rendering via CGContext. Supports Swift 5.0+, iOS 10.0+, and SwiftUI for iOS 14.0+.

Tokens
2.7K
Snippets
6
Records
8
Agent score
4%

What's inside DanmakuKit

  1. How DanmakuKit works: CellModels and Cells

    master

    DanmakuKit uses a decoupled architecture where data and rendering are separated into two main components:

    1. DanmakuCellModel: A data object that holds the properties of a single danmaku (e.g., text, font, duration, type). You must subclass this to define your custom data.
    2. DanmakuCell: A view object responsible for the actual drawing. You must subclass this and override the displaying(_:_:_:) method to perform drawing using CGContext.

    Important Threading Note: The displaying method in DanmakuCell is called on a background thread, not the main thread. Ensure your drawing logic and data access are thread-safe.

    class MyModel: DanmakuCellModel { ... }
    class MyCell: DanmakuCell { 
        override func displaying(_ context: CGContext, _ size: CGSize, _ isCancelled: Bool) { ... }
    }
  2. Install DanmakuKit via Swift Package Manager

    master

    To install DanmakuKit using Swift Package Manager, add the dependency to your Package.swift file:

    dependencies: [
        .package(url: "https://github.com/qyz777/DanmakuKit.git", from: "1.6.0"),
    ],
    targets: [
        .target(name: "YourTarget", dependencies: ["DanmakuKit"]),
    ]
  3. Implement a custom text danmaku

    master

    To create a custom text danmaku, follow these three steps:

    1. Subclass DanmakuCellModel

    Define your properties (text, font, etc.) and implement calculateSize() to determine the danmaku's dimensions. You must also specify the cellClass to link it to your custom view.

    2. Subclass DanmakuCell

    Override displaying(_:_:_:) to draw the content using the provided CGContext.

    3. Shoot the danmaku into DanmakuView

    Initialize your model, configure its properties, and call shoot(danmaku:) on your DanmakuView instance.

    // 1. Define Model
    class DanmakuTextCellModel: DanmakuCellModel {
        var text = ""
        var font = UIFont.systemFont(ofSize: 15)
        var cellClass: DanmakuCell.Type { return DanmakuTextCell.self }
        var size: CGSize = .zero
        
        func calculateSize() {
            size = NSString(string: text).boundingRect(with: CGSize(width: CGFloat(Float.infinity), height: 20), options: [.usesFontLeading, .usesLineFragmentOrigin], attributes: [.font: font], context: nil).size
        }
        
        func isEqual(to cellModel: DanmakuCellModel) -> Bool { return true }
    }
    
    // 2. Define View
    class DanmakuTextCell: DanmakuCell {
        override func displaying(_ context: CGContext, _ size: CGSize, _ isCancelled: Bool) {
            guard let model = model as? DanmakuTextCellModel else { return }
            let text = NSString(string: model.text)
            let attributes: [NSAttributedString.Key: Any] = [.font: model.font, .foregroundColor: UIColor.white]
            text.draw(at: .zero, withAttributes: attributes)
        }
    }
    
    // 3. Use DanmakuView
    let danmakuView = DanmakuView(frame: CGRect(x: 0, y: 0, width: 350, height: 250))
    let cellModel = DanmakuTextCellModel()
    cellModel.text = "Hello World"
    cellModel.calculateSize()
    danmakuView.shoot(danmaku: cellModel)
    danmakuView.play()
  4. How to draw custom Danmaku

    master

    Drawing danmaku in DanmakuKit follows a three-step process involving a model, a view, and the main view controller.

    1. Implement a DanmakuCellModel

    Create a class that inherits from DanmakuCellModel. You must define properties for your content and override cellClass to return your custom DanmakuCell type. You should also implement calculateSize() to determine the dimensions of the danmaku.

    2. Implement a DanmakuCell

    Create a class that inherits from DanmakuCell. You must override the displaying(_:_:_:) method. This method provides a CGContext where you use Core Graphics to draw your content.

    Important: The displaying method may be called on a background thread, so ensure your drawing logic is thread-safe.

    3. Shoot the Danmaku to DanmakuView

    Initialize a DanmakuView, create your model instance, configure its properties (like text, displayTime, and type), and call shoot(danmaku:) to add it to the view.

    // 1. Model
    class DanmakuTextCellModel: DanmakuCellModel {
        var identifier = ""
        var text = ""
        var font = UIFont.systemFont(ofSize: 15)
        var offsetTime: TimeInterval = 0
        var cellClass: DanmakuCell.Type { return DanmakuTextCell.self }
        var size: CGSize = .zero
        var track: UInt? 
        var displayTime: Double = 8
        var type: DanmakuCellType = .floating
        var isPause = false
    
        func calculateSize() {
            size = NSString(string: text).boundingRect(with: CGSize(width: CGFloat(Float.infinity), height: 20), options: [.usesFontLeading, .usesLineFragmentOrigin], attributes: [.font: font], context: nil).size
        }
    
        func isEqual(to cellModel: DanmakuCellModel) -> Bool {
            return identifier == cellModel.identifier
        }
    }
    
    // 2. View
    class DanmakuTextCell: DanmakuCell {
        required init(frame: CGRect) { super.init(frame: frame); backgroundColor = .clear }
        required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
        override func displaying(_ context: CGContext, _ size: CGSize, _ isCancelled: Bool) {
            guard let model = model as? DanmakuTextCellModel else { return }
            let text = NSString(string: model.text)
            context.setLineWidth(1)
            context.setLineJoin(.round)
            context.setStrokeColor(UIColor.white.cgColor)
            context.saveGState()
            context.setTextDrawingMode(.stroke)
            let attributes: [NSAttributedString.Key: Any] = [.font: model.font, .foregroundColor: UIColor.white]
            text.draw(at: .zero, withAttributes: attributes)
            context.restoreGState()
            context.setTextDrawingMode(.fill)
            text.draw(at: .zero, withAttributes: attributes)
        }
    }
    
    // 3. Usage
    let danmakuView = DanmakuView(frame: CGRect(x: 0, y: 0, width: 350, height: 250))
    view.addSubview(danmakuView)
    let cellModel = DanmakuTextCellModel(json: nil)
    cellModel.displayTime = 8
    cellModel.text = "Hello Danmaku"
    cellModel.identifier = "unique_id"
    cellModel.calculateSize()
    cellModel.type = .floating
    danmakuView.shoot(danmaku: cellModel)
  5. Control Danmaku playback

    master

    Use the following methods on your DanmakuView instance to control the danmaku lifecycle:

    • play(): Starts displaying danmaku.
    • pause(): Pauses the current danmaku playback.
    • stop(): Stops displaying danmaku and cleans up resources. Note: Memory associated with DanmakuKit will not be released until this method is called. stop() is automatically called when the DanmakuView is deallocated.
    danmakuView.play()
    danmakuView.pause()
    danmakuView.stop()