iOS开发之如何用UITableView实时显示歌词

来源:互联网 发布:淘宝防排查防举报 编辑:程序博客网 时间:2024/04/29 21:56

前一段时间一直在捣鼓做一个网络音乐播放器,已经实现了基本功能,能加载网络上专辑图片、歌曲和歌词,能播放、暂停、上一曲、下一曲,能选歌播放。现在想要加入显示歌词的功能,上网查了一下资料,大家都是用UITableView来显示歌词的,自己试着整一下,下面来分享一下我的成果!


先说一下思路:

1、将歌词中的内容解析出来,时间点和歌词内容要存储到一个存放歌词信息的类中。

2、建立UITableView,一行歌词一行cell(前面解析歌词时可以知道总共有多少行歌词)。

3、根据音乐播放的进度调整显示哪一行,给当前显示的cell添加一个小动画,增强用户体验。


这里为了方便,直接从storyboard拉一个UITableView,再拉一个cell,拉一个label,拉两个button,如图1所示:


图1

这里的cell是自定义cell,label是放到cell中铺满整个cell,因为歌词显示一行一个cell,cell只显示label,这样歌词的行距就一样了。自定义cell要设置identifier,这里设置为lrcCell,如图2所示:



图2


为UITableView新建一个class,名字为SCTableView,这个类封装了歌词解析(歌词格式如图3所示)和歌词显示的方法,其中代码如下:

////  SCTableView.swift//  playerTest4////  Created by 凌       陈 on 7/15/17.//  Copyright © 2017 凌       陈. All rights reserved.//import UIKitclass SCTableView: UITableView, UITableViewDelegate , UITableViewDataSource{        var mCurrentSongIndex = 0    var mUpdateTimer : Timer?    var mLRCDictinary : [String : String] = [String : String]()    var mTimeArray : [String] = [String]()    var mIsLRCPrepared : Bool = false    var mLineNumber : Int = -1        var currentCell : SCLrcCell?        var lrcOldCell : SCLrcCell?        var old_Index : Int = 0            var lrcProgress : CGFloat = 0.0{                didSet{                        if currentCell != nil {                                autoUpdateLrc()                            }        }            }        // 设置歌词index,显示哪一行    var Lrc_Index : Int = 0 {                didSet{                        if Lrc_Index == oldValue {                return            }                        // 滚动到指定index的位置            // 新的indexPath            let indexPath = NSIndexPath(row: Lrc_Index, section: 0  )            self.scrollToRow(at: indexPath as IndexPath, at: .middle, animated: true)            currentCell = self.cellForRow(at: indexPath as IndexPath) as? SCLrcCell            currentCell?.mTitleLable.textColor = UIColor.red            // 旧的indexPath            let oldIndexpath = NSIndexPath(row: oldValue, section: 0)            let oldCell = self.cellForRow(at: oldIndexpath as IndexPath) as? SCLrcCell            old_Index = oldValue            lrcOldCell = oldCell            currentCell?.addAnimation(animationType: .scaleAlways)            oldCell?.addAnimation(animationType: .scaleNormal)            oldCell?.mTitleLable.textColor = UIColor.black            //            oldCell?.lrcCell_textLabel?.progress = 0        }            }    required init?(coder aDecoder: NSCoder) {        super.init(coder: aDecoder)                self.dataSource = self        self.delegate = self                    }        //MARK: -  解析歌词    func prepareLRC(lrcPath:String) {        //从资源中导入 lrc        let url = Bundle.main.url(forResource: lrcPath, withExtension: nil)                var contentStr = ""        do{            contentStr = try String(contentsOf: url!)        }catch{                        print(error)                    }                let lrcArray = contentStr.components(separatedBy: "\n")                self.mLRCDictinary = [String : String]()        self.mTimeArray = [String]()                for line in lrcArray {                        // 首先处理歌词中无用的东西             // [ti:][ar:][al:]这类的直接跳过            if line.contains("[0") || line.contains("[1") || line.contains("[2") || line.contains("[3") {                                var lineArr = line.components(separatedBy:"]")                let str1 = (line as NSString).substring(with: NSRange(location: 3,length: 1))                let str2 = (line as NSString).substring(with: NSRange(location: 6,length: 1))                if str1 == ":" && str2 == "." {                    let lrcStr = lineArr[1]                    let timeStr = (lineArr[0] as NSString).substring(with: NSRange(location: 1, length: 5))                    self.mLRCDictinary[timeStr] = lrcStr                    self.mTimeArray.append(timeStr)                                    }            } else {                continue            }                    }                self.mIsLRCPrepared = true        self.reloadData()    }        func updateLRC(currentTime:CGFloat) {        for i in 0..<self.mLRCDictinary.count {            var timeArr = self.mTimeArray[i].components(separatedBy:":")            let time = CGFloat(Int(timeArr[0])!) * 60 + CGFloat(Int(timeArr[1])!)            if i + 1 < self.mTimeArray.count {                var timeArr1 = self.mTimeArray[i + 1].components(separatedBy:":")                let time1 = CGFloat(Int(timeArr1[0])!) * 60 + CGFloat(Int(timeArr1[1])!)                                if currentTime > time && currentTime < time1 {                    self.mLineNumber = i                    self.reloadData()                    self.selectRow(at: NSIndexPath(row: i, section: 0) as IndexPath, animated: true, scrollPosition: UITableViewScrollPosition.middle)                }            }        }    }        /// 自动更新歌词进度方法    fileprivate func autoUpdateLrc()  {        //        let lrcModel = lrcModels[Lrc_Index]        //        lrcModel.progress = lrcProgress        //        lrcModels[Lrc_Index] = lrcModel        let indexPath = NSIndexPath(row: Lrc_Index, section: 0) as IndexPath                let count = self.visibleCells        if count.count <= 0 || indexPath.row > self.mTimeArray.count - 1 {            return        }                guard (self.cellForRow(at:indexPath ) != nil)  else {            return        }        let cell = self.cellForRow(at:indexPath )            }            //MARK: - Table Delegate    func numberOfSectionsInTableView(tableView: UITableView) -> Int {        return 1    }        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {        return self.mTimeArray.count    }        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {        let cell = tableView.dequeueReusableCell(withIdentifier: "lrcCell") as! SCLrcCell                cell.mTitleLable.text = self.mLRCDictinary[self.mTimeArray[indexPath.row]]                cell.backgroundColor = UIColor.clear        cell.selectionStyle = UITableViewCellSelectionStyle.none                cell.mTitleLable.numberOfLines = 0        cell.mTitleLable.lineBreakMode = NSLineBreakMode.byWordWrapping        cell.mTitleLable.sizeToFit()                if self.mLineNumber == indexPath.row {            cell.mTitleLable.font = UIFont.systemFont(ofSize: 24)            cell.mTitleLable.textColor = UIColor.red        } else {            cell.mTitleLable.font = UIFont.systemFont(ofSize: 20)            cell.mTitleLable.textColor = UIColor.black        }                return cell    }           }


                                                                图3

将UITableView的class设置为SCTableView,方法如图4所示:


图4

同样,为UITableViewCell新建一个class,名字为SCLrcCell,其中代码如下:


////  SRLrcCell.swift//  TestRadio////  Created by Wentao on 15/5/14.//  Copyright (c) 2015年 Wentao. All rights reserved.//import UIKitclass SCLrcCell: UITableViewCell {        // 这个是storyboard拉的label的outlet    @IBOutlet weak var mTitleLable: UILabel!    override func awakeFromNib() {        super.awakeFromNib()        // Initialization code    }    override func setSelected(_ selected: Bool, animated: Bool) {        super.setSelected(selected, animated: animated)        // Configure the view for the selected state    }}
将UITableViewCell的class设置为SCLrcCell。

新建一个class,名字为SCCellAnimation,这个类处理cell动画,其代码如下:

import UIKitenum SC_AnimationType {    case translation    case scale    case rotation    case scaleAlways    case scaleNormal}extension UITableViewCell{    func addAnimation(animationType : SC_AnimationType)  {        switch animationType {        case .translation:                        layer.removeAnimation(forKey: "translation")                            let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")            animation.values = [50 , 0 , 50 , 0];            animation.duration = 0.7            animation.repeatCount = 1            layer.add(animation, forKey: "translation")                case .scale:                        layer.removeAnimation(forKey: "scale")            let animation = CAKeyframeAnimation(keyPath: "transform.scale.x")            animation.values = [0.5, 1.0 ];            animation.duration = 0.7            animation.repeatCount = 1            animation.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)];                        layer.add(animation, forKey: "scale")        case .rotation:                    layer.removeAnimation(forKey: "rotation")                    let animation = CAKeyframeAnimation(keyPath: "transform.rotation.z")            animation.values = [-1 / 6 * Double.pi , 0 , 1 / 6 * Double.pi , 0];            animation.duration = 0.7            animation.repeatCount = 1            layer.add(animation, forKey: "rotation")                    case .scaleAlways :            //layer.removeAnimation(forKey: "scale")            let animation = CAKeyframeAnimation(keyPath: "transform.scale.x")            animation.values = [1.0, 1.2 ];            animation.duration = 0.7            animation.repeatCount = 1            animation.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)];            animation.isRemovedOnCompletion = false            animation.fillMode = kCAFillModeForwards;            layer.add(animation, forKey: "scale")                    case .scaleNormal:                       let animation = CAKeyframeAnimation(keyPath: "transform.scale.x")            animation.autoreverses = true            animation.duration = 0.7            animation.repeatCount = 1            animation.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)];                        layer.add(animation, forKey: "scale")                    default: break                    }    }}

在UIViewController中添加如下代码:

////  ViewController.swift//  playerTest4////  Created by 凌       陈 on 7/15/17.//  Copyright © 2017 凌       陈. All rights reserved.//import UIKitclass ViewController: UIViewController {        var lrcIndex = 0    @IBOutlet weak var mLrcTable: SCTableView!    override func viewDidLoad() {        super.viewDidLoad()        // Do any additional setup after loading the view, typically from a nib.                self.mLrcTable.separatorStyle = .none// 去掉tableView分割线        self.mLrcTable.showsVerticalScrollIndicator = false// 去掉tableView垂直滚动条        self.mLrcTable.prepareLRC(lrcPath: "309769.lrc")// 歌词解析    }    override func didReceiveMemoryWarning() {        super.didReceiveMemoryWarning()        // Dispose of any resources that can be recreated.    }    // 上一行    @IBAction func preLine(_ sender: Any) {        if lrcIndex > 0 {            lrcIndex  = lrcIndex - 1            self.mLrcTable.Lrc_Index = lrcIndex        }    }        // 下一行    @IBAction func nextLine(_ sender: Any) {        if lrcIndex < self.mLrcTable.mTimeArray.count - 1 {            lrcIndex  = lrcIndex + 1            self.mLrcTable.Lrc_Index = lrcIndex        }    }}
把歌词添加进工程就可以运行了,运行结果:



源代码已经放到github,地址:https://github.com/Mozartisnotmyname/LRCShow.git


原创粉丝点击