SKSLyricsLabel 1.0.3

SKSLyricsLabel 1.0.3

CoderChan维护。



  • 作者:
  • 陈振超

SKSLyricsLabel

☆☆☆ “iOS卡拉OK歌词效果Label” ☆☆☆

### 支持 CocoaPods 导入

pod 'SKSLyricsLabel', '>= 1.0.3'

### 更改记录:
2020.10.15 -- v1.0.3 初始版本,动态改变多行控件的最大宽度
2020.10.15 -- v1.0.2 修复多行控件反复点击时出现的异常
2020.10.09 -- v1.0.1 添加了一些注释
2020.09.29 -- v1.0.0 上传了第一版代码

复杂的iOS多行富文本卡拉OK控件现已实现简化式写法,更多属性可查看源码和demo,记得star哦

// 最简单的单行普通效果
let oneLineNormalLabel = SKSOneLineLyricsLabel()
oneLineNormalLabel.text = "我是一个单行效果的卡拉OK歌词控件文本"
oneLineNormalLabel.attributeStr = NSAttributedString对象
oneLineNormalLabel.maskColor = 卡拉OK效果颜色
self.view.addSubview(oneLineNormalLabel)

oneLineNormalLabel.duration = <#在文本赋值后设置,duration一旦赋值即可实现播放效果#>

// 相对复杂的富文本多行有行间距的效果
let mutiLineAttLabel = SKSMultiLineLyricsLabel(<#文本控件的最大宽度,为什么不自适应?可见下面的Q&A>)
mutiLineAttLabel.maskColor = 卡拉OK效果颜色
mutiLineAttLabel.lineSpace = 行间距
mutiLineAttLabel.attributeStr = NSAttributedString对象
self.view.addSubview(mutiLineAttLabel)

mutiLineAttLabel.playAnimation(<#可以在赋值后播放的时长,完成回调可忽略#>) {
    print("完成播放")
}

Q&A

SKSLyricsLabel 的思路是什么

在尝试其他方案后,我花了不少时间。由于 iOS 提供的 API 目前还没有可以用于这种效果的(如果有,欢迎联系我635961956),单行文本的话就简单多了,但需求需要兼容多行和富文本。于是我想出了一种将超长文本拆分为多行的方法,每一行都是一个单行,然后我就想到了使用 CoreText 对富文本进行按区域面积拆分的方法,经过一些挑战,才有了今天相对稳定的 SKSLyricsLabel。

SKSLyricsLabel的多行文本控件在初始化时需要传入一个宽度?

由于上述思路,拆分长文本需要一定的面积,这个面积需要宽和高,其中高可以由font.lineHeight设置,但是不同机型上计算出的font.lineHeight浮点数可能不完全相同,哪怕只是相差0.000001也会导致面积不足,无法容纳单元格内容,从而造成死循环(这个问题已经解决)拆分失败,因此将内部的NSAttributedString分类改成了需要传入宽高。以下是关键代码:

/// 对富文本进行平均分割区域
/// - Parameters:
///   - width: 分割单元的宽度
///   - height: ⚠️分割单元的高度,请谨慎使用font.lineHeight
/// - Returns: 分割后的富文本数组
public func sks_separatedAttLines(width: CGFloat, height: CGFloat) -> [NSAttributedString] {
    let textFrame = CGRect(x: 0, y: 0, width: width, height: height)
    let rectPath: CGPath = CGPath(rect: textFrame, transform: nil)

    var textPos = 0
    let cfAttStr: CFAttributedString = self as CFAttributedString

    let framesetter: CTFramesetter = CTFramesetterCreateWithAttributedString(cfAttStr)
    var pagingResult = [NSAttributedString]()

    while textPos < self.length {
        let frame: CTFrame = CTFramesetterCreateFrame(framesetter, CFRange(location: textPos, length: 0), rectPath, nil)
        let frameRange = CTFrameGetVisibleStringRange(frame)
        if frameRange.length == 0 {
          pagingResult.append(self)
          break
        }

        let range = NSRange(location: frameRange.location, length: frameRange.length)
        let subStr = self.attributedSubstring(from: range)
        pagingResult.append(subStr)
        textPos += frameRange.length
    }

    return pagingResult
}