描述
开 本: 16开纸 张: 胶版纸包 装: 平装是否套装: 否国际标准书号ISBN: 9787302441847
为方便读者学习,作者还为本书精心录制了“7天玩转iOS UI开发视频教程”,本视频教程包括基础篇、中级篇、高级篇、进阶篇、扩展篇5部分,总计36堂课,播放时长超过13小时。此外,本书还提供iOS UI开发视频教程源代码以及本书实例源代码。
本书的特色是通俗易学,突出实战,提供了大量开发案例,适合于刚入职或新手iOS开发人员和爱好者、大中专院校学生及iOS培训班学员,尤其适合有一定语言基础想要开发App产品的开发者。
目 录
第1章 开发准备 1
1.1 iOS 9新特性简述 2
1.1.1 新增压力传感器编程接口 2
1.1.2 全新的搜索功能API
2
1.1.3 更小、更快——全新的应用瘦身策略 3
1.1.4 使用更加安全的网络传输协议 4
1.2 熟悉iOS开发环境 4
1.2.1 安装Xcode开发工具 4
1.2.2 了解Xcode开发工具主界面 6
1.2.3 Xcode开发工具的使用技巧及常用快捷键 7
1.3 创建个iOS项目 8
1.4 使用Git进行项目版本管理 13
1.4.1 Git与Github简介 13
1.4.2 注册GitHub会员 13
1.4.3 使用Xcode创建Git仓库 14
1.4.4 用Xcode建立本地Git仓库与GitHub代码托管平台的联系 16
第2章 基础UI控件 19
2.1 iOS系统UI框架的介绍 20
2.1.1 MVC设计模式 20
2.1.2 代理设计模式 21
2.2 视图控制器——UIViewController
21
2.2.1 UIViewController的生命周期 21
2.2.2 UIViewController的视图层级结构 25
2.3 文本控件——UILabel
25
2.3.1 使用UILabel在屏幕上创建一个标签控件 26
2.3.2 自定义标签控件的相关属性 26
2.3.3 多行显示的UILabel与换行模式 27
2.4 按钮控件——UIButton
29
2.4.1 创建一个按钮来改变屏幕颜色 29
2.4.2 更加多彩的UIButton控件 32
2.5 文本输入框控件——UITextField
33
2.5.1 在屏幕上创建一个输入框 33
2.5.2 UITextField的常用属性介绍 35
2.5.3 UITextField的代理方法 36
2.5.4 实现一个监听输入信息的用户名输入框 37
2.6 开关控件——UISwitch
38
2.6.1 创建一个开关控件
38
2.6.2 为UISiwtch控件添加触发方法 39
2.7 分页控制器——UIPageControl
40
2.8 分段控制器——UISegmentedControl
41
2.8.1 UISegmentControl基本属性的应用 41
2.8.2 对UISegmentedControl中的按钮进行增、删、改操作 42
2.8.3 UISegmentedControl中按钮宽度的自适应 43
2.9 滑块控件——UISlider
43
2.9.1 UISlider的创建与常规设置 44
2.9.2 对UISlider添加图片修饰 45
2.10 活动指示器控件——UIActivityIndicatorView
45
2.11 进度条控件——UIProgressView
47
2.12 步进控制器——UIStepper
48
2.12.1 步进控制器的基本属性使用 48
2.12.2 自定义UIStepper按钮图片 49
2.13 选择器控件——UIPickerView
49
2.13.1 创建一个UIPickerView控件 50
2.13.2 UIPickerView选中数据时的回调代理 51
2.14 通过CALayer对视图进行修饰 52
2.14.1 创建圆角的控件
52
2.14.2 创建带边框的控件
52
2.14.3 为控件添加阴影效果
53
2.15 警告控制器——UIAlertController
54
2.15.1 UIAlertController的警告框 54
2.15.2 UIAlertController之活动列表 56
2.16 扩展篇 57
2.16.1 搜索栏控件——UISearchBar
57
2.16.2 日期时间选择器——UIDatePicker
59
2.16.3 警告视图——UIAlertView
61
2.16.4 活动列表——UIActionSheet
62
2.17 实战:登录注册界面的搭建 62
第3章 高级UI控件 68
3.1 导航控制器——UINavigationController
69
3.1.1 导航控制器的工作原理
69
3.1.2 使用导航控制器进行多界面搭建 70
3.1.3 导航栏UINavigationBar
73
3.1.4 导航按钮UIBarButtonItem
74
3.1.5 导航控制器的工具栏
77
3.1.6 iOS 8之后导航控制器的一些有趣功能 77
3.2 标签控制器——UITabBarController
78
3.2.1 标签控制器的工作原理
78
3.2.2 标签控制器的基础用法解析 78
3.2.3 关于UITabBarItem的使用 80
3.3 滚动视图——UIScrollView
81
3.3.1 使用UIScrollView展示视图内容 81
3.3.2 UIScrollView的代理方法 83
3.4 网络视图——UIWebView
84
3.4.1 App网络传输安全策略 85
3.4.2 通过网络请求加载UIWebView
86
3.4.3 通过HTML字符串加载UIWebView 86
3.4.4 通过NSData数据加载UIWebView 87
3.4.5 UIWebView中常用方法解析 88
3.4.6 UIWebView的代理方法 89
3.5 表格视图——UITableView
90
3.5.1 UITableView的创建与复用机制 90
3.5.2 创建一个表格视图UITableView
91
3.5.3 关于表格数据的载体UITableViewCell
93
3.5.4 设置UITableView的行高和头尾视图 95
3.5.5 UITableView的用户交互行为 96
3.5.6 为UITableView添加索引栏 99
3.6 复杂布局视图——UICollectionView
99
3.6.1 UICollectionView控件的优势与布局方式 100
3.6.2 使用UICollectionView进行九宫格式的布局 100
3.6.3 创建更加灵活的流式布局 102
3.6.4 自定义UICollectionViewFlowLayout进行参差瀑布流布局 103
3.6.5 使用UICollectionView进行圆环布局 106
3.7 实战:开发一款手机网页浏览器 109
3.7.1 网页浏览器工程的搭建
110
3.7.2 核心网页视图的设计
111
3.7.3 历史记录界面的设计
119
3.7.4 收藏界面的设计
122
3.7.5 启动页面、图标及应用名称的相关优化 124
第4章 网络编程 127
4.1 使用NSURLConnection请求网络数据 128
4.1.1 申请一个免费的API服务 128
4.1.2 使用NSURLConnection进行API服务数据的获取 131
4.1.3 使用NSURLConnection进行异步网络请求 132
4.1.4 使用NSURLConnection类通过代理回调的方式异步进行网络请求 134
4.2 设计封装一个更加易用的网络请求类 135
4.2.1 设计自定义的网络请求连接类 135
4.2.2 设计自定义的网络请求连接管理类 136
4.3 JSON类型数据的解析与数据模型的设计 139
4.3.1 JSON数据简介
139
4.3.2 在iOS中解析JSON数据 141
4.3.3 数据模型Model类的设计 142
4.4 使用CocoaPods进行第三方库的管理 146
4.4.1 在MAC上安装CocoaPods 146
4.4.2 用CocoaPods搭建一个使用第三方网络请求框架AFNetworking的工程 148
4.5 使用AFNetworking进行网络请求 150
4.5.1 详解HTTP/HTTPS协议 150
4.5.2 使用AFNetworking进行网络请求 151
4.6 实战:开发“笑一笑”应用程序 153
4.6.1 工程项目框架的搭建
154
4.6.2 “笑一笑”界面数据载体cell的设计 155
4.6.3 “笑一笑”界面的搭建
157
4.6.4 实现下拉刷新与加载更多功能 162
4.6.5 “趣图吧”界面数据载体cell的设计 164
4.6.6 “趣图吧”界面的设计
167
第5章 音视频开发 172
5.1 iOS音频开发基础——AVAudioPlayer类的使用 173
5.1.1 使用AVAudioPlayer进行MP3音频文件的播放 173
5.1.2 进行音频播放相关属性的控制 175
5.1.3 后台播放音频及用户交互的优化 180
5.2 iOS视频开发基础
184
5.2.1 使用MPMoviePlayerController向应用中嵌入视频模块 184
5.2.2 MPMoviePlayerController常用属性与方法解析 185
5.3 视频播放器视图控制器——MPMoviePlayerViewController
189
5.4 AVPlayerViewController视频播放框架与画中画开发技术 191
5.4.1 使用AVPlayerViewController进行视频播放 191
5.4.2 iPad的画中画播放技术 193
5.5 实战:“天后王菲”音频播放器的开发 195
5.5.1 工程搭建与LRC歌词文件简介 196
5.5.2 LRC歌词解析引擎的设计 197
5.5.3 核心播放器引擎的设计
201
5.5.4 歌曲列表与歌词显示视图界面的设计 208
5.5.5 播放器主页面的实现
213
5.5.6 后台播放音频用户交互的处理 219
第6章 动画开发 221
6.1 使用UIImageView播放图片组帧动画 222
6.2 UIView层动画的应用
223
6.2.1 执行UIView层过渡动画的三个类方法 223
6.2.2 创建UIView层的阻尼动画 225
6.2.3 动画参数配置与组合动画 225
6.2.4 UIView层过渡动画支持的属性 227
6.3 使用commit方式进行UIView层动画的创建 228
6.3.1 使用commit方式进行UIView层过渡动画的创建 228
6.3.2 两种UIView层动画创建方式的优劣 230
6.4 UIView的转场动画
230
6.4.1 重绘UIView视图时使用的转场动画 230
6.4.2 切换UIView视图时使用的转场动画 231
6.5 核心动画编程技术——CoreAnimation
232
6.5.1 锚点对视图控件几何位置的影响 233
6.5.2 色彩梯度层——CAGradientLayer
234
6.5.3 视图拷贝层——CAReplicatorLayer
235
6.5.4 图形渲染层——CAShapeLayer
236
6.5.5 文本绘制层——CATextLayer
237
6.5.6 CAAnimation动画体系介绍 238
6.5.7 使用CABasicAnimation创建基础动画 240
6.5.8 使用CAKeyframeAnimation类创建关键帧动画 242
6.5.9 CALayer层的转场动画——CATransition 243
6.5.10 CALayer层的组合动画——CAAnimationGroup 245
6.5.11 CATransform3D变换的应用 246
6.6 炫酷的粒子效果 248
6.6.1 粒子发射器——CAEmitterLayer
248
6.6.2 粒子单元——CAEmitterCell
250
6.6.3 创建粒子火焰动画
251
6.7 播放GIF动态图 253
6.7.1 使用UIWebView进行GIF动态图播放 253
6.7.2 使用UIImageView帧动画进行GIF动态图播放 254
6.8 实战:小游戏Flappy
Bird的设计与开发 256
6.8.1 小鸟对象的设计
257
6.8.2 游戏开始界面的设计
259
6.8.3 游戏结束界面的设计
261
6.8.4 Flappy Bird游戏主框架的搭建 262
第7章 传感器开发 270
7.1 为应用程序添加手机密码及指纹识别的安全验证 271
7.1.1 使用手机密码为应用程序添加安全验证 271
7.1.2 使用用户指纹为应用程序添加安全验证 273
7.2 使用加速度传感器、螺旋仪传感器与磁力传感器获取设备空间状态 274
7.2.1 使用UIAccelerometer获取设备空间状态 274
7.2.2 使用CoreMotion框架获取设备空间状态信息 275
7.3 距离传感器的应用 278
7.4 iOS蓝牙开发技术
279
7.4.1 中心设备管理类CBCentralManager
280
7.4.2 外围设备管理类CBPeripheralManager
285
7.5 GPS应用与地图编程技术
289
7.5.1 进行设备地理位置定位
289
7.5.2 原生地图开发技术
292
7.5.3 在地图中添加大头针及标注 294
7.5.4 在地图视图中添加覆盖物 297
7.5.5 在地图中进行线路导航与附近兴趣点检索 299
7.6 实战:简易蓝牙对战五子棋
304
7.6.1 游戏核心通信类的设计
304
7.6.2 棋盘瓦片的设计
314
7.6.3 核心游戏视图与游戏核心逻辑的设计 315
7.6.4 核心游戏视图控制器的设计 325
第8章 界面布局 329
8.1 iOS中传统的UIViewAutoresizing布局模式 330
8.1.1 通过代码来设置视图控件的UIViewAutoresizing模式 330
8.1.2 在xib文件中可视化地配置控件的autoresizing属性 332
8.2 Autolayout自动布局框架 333
8.2.1 初识Autolayout
334
8.2.2 Autolayout的属性意义与一个简单的自动布局示例 335
8.2.3 使用Objective-C风格的方法进行代码Autolayout布局 338
8.2.4 使用格式化的字符串进行Autolayout布局对象的创建 341
8.2.5 与约束相关的几个方法
343
8.2.6 使用Autolayout设计一个高度自适应的聊天输入框及动画优化 343
8.2.7 使用第三方库Masonry进行Autolayout约束布局 345
第9章 数据持久化 351
9.1 使用plist文件进行轻量级数据持久化管理 352
9.1.1 在工程中读取plist文件数据 352
9.1.2 在程序沙盒Doucments目录中创建和使用plist文件 353
9.1.3 使用NSUserDefaults类进行数据持久化 354
9.2 使用归档技术进行数据模型持久化 356
9.2.1 进行单一系统数据类型的归档与解归档操作 356
9.2.2 对多个对象进行数据归档 357
9.2.3 进行自定义数据模型的归档 358
9.3 小型数据库SQLite在iOS开发中的应用 360
9.3.1 SQLite数据库常用语法介绍 360
9.3.2 使用iOS原生框架sqlite3对SQLite数据库进行操作 362
9.4 核心数据管理框架CoreData的使用 367
9.4.1 使用CoreData设计数据模型 367
9.4.2 CoreData编程框架中3个重要的类 370
9.4.3 CoreData编程框架的数据操作 373
9.4.4 使用CoreData进行数据与页面的绑定 378
9.5 网络缓存策略 384
9.5.1 为网络请求设置缓存策略 384
9.5.2 应用缓存管理类NSURLCache简介 385
第10章 提交应用程序到AppStore 387
10.1 使用Xcode开发工具进行程序调试 388
10.1.1 使用自定义断点进行代码调试 388
10.1.2 添加全局异常断点
389
10.1.3 使用LLDB调试器进行程序调试 390
10.2 Apple开发者账号的申请 391
10.2.1 几种类型的开发者账号 391
10.2.2 申请开发者账号的过程 391
10.3 进行应用程序的打包
394
10.3.1 在iTunes
Connect中进行应用的创建与配置 394
10.3.2 使用Xcode进行打包与提交iTunes 401
第11章 进阶技巧 405
11.1 Objective-C中block语法的应用 406
11.1.1 声明与实现block语法块 406
11.1.2 block代码块中访问对象的微妙关系 407
11.2 iOS通知中心NSNotificationCenter的应用 408
11.2.1 通知类NSNotification简介 409
11.2.2 通知中心NSNotificationCenter应用 409
11.3 多线程开发技术 410
11.3.1 使用NSThread进行线程管理 411
11.3.2 使用NSOperation类与NSOperationQueue类进行多任务管理 412
11.3.3 iOS中GCD编程技术简介 416
前 言
编写本书的目的
近些年,移动端应用的开发越来越火热,市场对移动端两大主流操作系统平台Android与iOS开发者的需求量也大幅增加。很多大学毕业生或者相关行业从业者都有进入移动开发领域的想法,本书成书的原因,也正是为了解决这类群体人员的学习困扰。
开发一款完整的iOS软件是一个复杂的过程,开发者除了需要有编程语言的基础外,还需要对程序设计有宏观的把控。目前市面上大多数的教材,要么过于理论基础,看不到实在的学习效果,消磨读者兴趣;要么入门台阶过高,使读者无法顺利地进行学习。本书在编写时,定位的目标是帮助并无太多基础的读者快速上手iOS应用开发,通过一本书,完整地了解iOS移动应用开发的整个过程,并且有能力自己开发一款常规的iOS移动应用。要做到这一点并不容易,在编写时除了要做到“事必躬亲”,不遗漏任何一个操作细节外,还要在讲解中插入完整的实战演示,以便读者能够学以致用,以用为学。
本书主要内容
本书分11章,下面介绍各章的主要内容与之间的联系。
第1章是为学习应用开发做准备,其中将介绍开发环境的搭建与开发工具的使用,这一章虽然为准备阶段,但对初学者来说却至关重要。
第2章将介绍iOS开发中的一些基础UI控件,移动端应用一个很重要的特点就是要有绚丽的界面,应用程序的界面决定了用户使用这款应用程序的体验与心情,这一章向读者独立地介绍每个基础控件的用法,并通过实战提高读者综合使用这些控件的能力。
第3章在第2章的基础上,将向读者介绍iOS开发中经常使用到的更多高级控件的用法,同样也会为读者提供实战机会。
第4章主要讲解了iOS应用开发中的网络编程技术,由于网络编程的演示需要有网络数据支持,很多有关网络教学的文档书籍都只讲授理论,没有办法使读者切身地进行测试与练习。在编写本章时,特意注意了这个问题,本章除了讲授网络编程在iOS应用开发中的相关知识外,还将教读者如何使用网上免费的API服务真正做出一款网络应用。
第5章主要讲解iOS应用程序开发中的音频与视频技术,这类技术在开发音频软件和视频软件中意义重大。
第6章将作为动画专题向读者介绍iOS应用开发中的动画技术,章节设计由易到难,并且都配有代码演示。
第7章将作为传感器专题向读者介绍iOS开发中可以调用的设备传感器的相关知识。
第8章是界面布局专题,笔者参阅了很多iOS教材,其中都没有过多提到界面布局的相关知识,这是一个十分大的弊端,界面布局技术是衡量一个开发者是否合格的重要指标,笔者相信读者学习iOS开发技术,不只是想简简单单地做出一个Demo自己玩,做出“产品”才是读者的真正目标,而一款成熟的产品一定是具有兼容性的,一定是优雅的。因此,本书特别将iOS界面布局技术作为单独的一章来向读者介绍。
第9章是数据持久化专题,本章将介绍有关iOS应用开发中的文件操作及数据库操作的相关知识。
通过前9章的学习,读者能够具备独立开发一款iOS应用的基本能力,但是仅仅做出产品还不够,如何让自己的产品在市场发布,使用户可以下载使用也是开发者不得不了解学习的内容,第10章将完整地向读者介绍提交自己的应用到App Store的整个过程。
第11章是进阶内容,此章也是读者开发能力提升的一章,本章将介绍一些独立于前面章节,但在实际开发中举足轻重的编程技术。
配书资源下载
为方便读者学习,作者还为本书精心录制了“7天玩转iOS UI开发视频教程”,本视频教程包括基础篇、中级篇、高级篇、进阶篇、扩展篇5部分,总计36堂课,播放时长超过13小时。此外,本书还提供iOS UI开发视频教程源代码以及本书实例源代码。
读者可通过以下地址:http://pan.baidu.com/s/1qXC2I0c,获取本书UI视频教程及源代码。
读者也可以关注微信公众号D11223344L来获取iOS学习的相关资料或者加QQ群203317592与更多志同道合的朋友一起学习iOS开发技术。
如果读者遇到下载问题,请发电子邮件至[email protected]联系,邮件主题为“求iOS开发实战配书资源”。
致谢
从接到出版社编辑的约稿邀请到本书初稿的完成,已经过去半年有余。本书中大部分内容都是在这半年间深夜的台灯下完成的。看着自己的作品,除了欣慰之外,更多地感觉到编程与代码已经成为了笔者生活中的一部分,除了工作与生计,从编程中得到的喜悦与满足感才是这项技能带给笔者的的礼物。同样,也希望读者热爱编程,从本书中除了学习到实用的技能外,还可以收获到更多乐趣。
在艺术的领域内,学无止境,编程也是一门艺术。笔者阅历尚浅,能力十分有限,在编写本书时,本力图将完整的开发技巧与从事编程行业所积累的经验毫无保留地与读者分享,但在成书时,有些内容依然无法完整表达,笔者也相信,本书中可能会出现一些错误和遗漏,读者有任何疑问或建议,都可直接联系笔者本人。在生活中,笔者除了是一位全职开发者之外,也是一名编程教师,期待与读者互相帮助,共同进步。
后,本书得以顺利完成,要感谢所有帮助过笔者的老师和朋友。感谢麦子学院CEO张凌华先生对笔者教学的鼓励和支持,感谢笔者的入门导师吕志轩老师,感谢支持笔者工作与写作的家人和笔者的女朋友,重要的,感谢王金柱编辑在笔者写作期间一直给予的帮助和指导,没有他们,本书不可能来到读者的手中。
珲少
2016年5月25日
5.5 实战:“天后王菲”音频播放器的开发
在本小节中读者将通过开发一款音乐播放器类应用程序学习到更多技巧,通过歌词同步引擎的开发,读者将初步掌握iOS中数据解析的相关方法。
5.5.1 工程搭建与LRC歌词文件简介
一款流行的音乐播放器软件,在播放音乐时同步显示歌词是的功能,其实这些播放器软件都实现了一个歌词解析引擎,通过歌词解析引擎将LRC文件解析为程序中需要的歌词对象。
LRC是Lyric单词的缩写,是音频同步歌词文件的一种协议格式。LRC文件中通过标签的形式将歌曲的专辑、歌手、每个时间点对应的歌词记录其中,音频播放器通过解析这些数据来同步显示歌词。一个LRC歌词文件其内容格式大致如下所示:
[ti:匆匆那年]
[ar:王菲]
[al:电影《匆匆那年》主题曲]
[t_time:(04:08)]
[00:32.16] 匆匆那年 我们究竟说了几遍
[00:34.82] 再见之后再拖延
[00:37.62] 可惜谁有没有爱过
[00:39.37] 不是一场七情上面的雄辩
[00:43.42] 匆匆那年 我们一时匆忙撂下
[00:45.82] 难以承受的诺言 只有等别人兑现
[00:54.69] 不怪那吻痕 还没积累成茧
[01:00.44] 拥抱着冬眠 也没能羽化再成仙
[01:05.74] 不怪这一段情没空反复再排练
[01:11.24] 是岁月宽容恩赐 反悔的时间
[01:22.38] 如果再见不能红着眼
[01:25.38] 是否还能红着脸
[01:28.24] 就像那年匆促刻下
[01:30.14] 永远一起那样美丽的谣言
[01:33.49] 如果过去还值得眷恋
[01:36.89] 别太快冰释前嫌
[01:39.44] 谁甘心就这样
[01:42.39] 彼此无挂也无牵
[01:45.93] 我们要互相亏欠
[01:50.94] 要不然凭何怀念
[02:02.25] 匆匆那年 我们见过太少世面
[02:04.79] 只爱看同一张脸
[02:07.65] 那么莫名其妙 那么讨人欢喜
[02:10.25] 闹起来又太讨厌
[02:13.30] 相爱那年活该匆匆
[02:15.51] 因为我们不懂顽固的诺言
[02:18.85] 只是分手的前言
[02:24.65] 不怪那天太冷 泪滴水成冰
[02:30.55] 春风也一样没吹进凝固的照片
[02:35.70] 不怪每一个人没能完整爱一遍
[02:41.35] 是岁月善意落下 残缺的悬念
[02:52.36] 如果再见不能红着眼 是否还能红着脸
歌词文件中ti标签对应歌曲的名称,ar标签对应歌手名称,al标签对应歌曲专辑。t_time对应歌曲的时间,之后的时间标签代表某一时刻对应的歌词。
使用Xcode创建一个名为MyPlayer的工程,向其中导入一些音频文件及其对应的LRC歌词文件。需要注意的是,LRC文件的文件名要与对应的歌曲名相同,便于在解析时将歌词文件与歌曲对应。
在向工程中添加大量文件时,可以通过建立新的引用目录使工程的目录结构看起来整齐一些,在Xcode文件导航区单击右键,选择New Group选项即可新建一个引用目录,如图5-30所示。
5.5.2 LRC歌词解析引擎的设计
在Xcode中创建两个引用目录Song和LRC,分别用来存放歌曲文件与歌词文件。设计一个新的类作为每行歌词的数据模型,使用Xcode新建一个类文件,命名为LRCItem,使其继承于NSObject。
在LRCItem.h文件中添加如下方法和属性的声明。
@interface LRCItem : NSObject
@property (nonatomic) float time;
@property (nonatomic,copy) NSString *lrc;
//排序方法
– (BOOL)isTimeOlderThanAnother:(LRCItem *)item;
@end
上面声明的属性和方法中,float是此行歌词的出现时间,lrc是此行歌词具体的文本数据。IsTimeOlderThanAnother:方法用于行歌词数据模型的排序,这个方法将按照时间先后进行排序。
在LRCItem.m文件中添加方法的实现代码。
– (BOOL)isTimeOlderThanAnother:(LRCItem *)item{
return self.time > item.time;
}
再新建一个类文件命名为LRCEngine作为歌词解析引擎,使其继承于NSObject。在LRCEngine.h文件中引入LRCItem类的头文件:
#import “LRCItem.h”
在LRCEngine.h文件中声明如下的属性与方法:
@interface LRCEngine : NSObject
-(instancetype)initWithFile:(NSString *)fileName;
@property(nonatomic,strong)NSString * author;
@property(nonatomic,strong)NSString * albume;
@property(nonatomic,strong)NSString * title;
-(void)getCurrentLrcInLRCArray:(void(^)(NSArray * lrcArray,int
currentIndex))handle atTime:(float)time;
@end
在上面代码中,initWithFile:方法用于进行歌词引擎的初始化,flieName参数为歌词文件的名称。author属性为歌手姓名。albume属性为专辑的名称。title属性为歌曲的名称。getCurrentLRCInLRCArray:atTime:方法为歌词引擎的核心方法,其通过传入一个时间点的值来获取当前对应的歌词,在handle函数块中将传入两个参数,一个是已经按时间排序的每行歌词数据的数组,一个是当前对应的歌词在数组中的位置。
在LRCEngine.m文件中声明一个可变数组用于存放每行歌词数据,代码如下:
@implementation LRCEngine
{
NSMutableArray * _lrcArray;
}
@end
实现LRCEngine类的初始化方法,代码如下:
-(instancetype)initWithFile:(NSString *)fileName{
if (self=[super init]) {
_lrcArray = [[NSMutableArray
alloc]init];
[self
creatDataWithFile:fileName];
}
return self;
}
上面方法中进行数组对象的初始化操作,creatDataWithFile:方法的实现如下:
-(void)creatDataWithFile:(NSString *)fileName{
//读取文件
NSString * lrcPath = [[NSBundle
mainBundle]pathForResource:fileName ofType:@”lrc”];
NSError * error;
NSString * dataStr = [NSString
stringWithContentsOfFile:lrcPath encoding:NSUTF8StringEncoding
error:&error];
//去掉/r
NSMutableString * tmpStr =
[[NSMutableString alloc]init];
NSArray * tmpArray = [dataStr
componentsSeparatedByString:@”r”];
for (int i=0; i
[tmpStr
appendString:tmpArray[i]];
}
//按照换行符进行字符串分割
NSArray * lrcArray = [tmpStr
componentsSeparatedByString:@”n”];
//数据解析并将空数据去掉
for (NSString * lrcStr in lrcArray) {
if (lrcStr.length==0) {
continue;
}
//判断是歌词数据还是文件信息数据
unichar c = [lrcStr
characterAtIndex:1];
if (c>=’0’&&c<=’9′)
{
//是歌词数据
[self getLrcData:lrcStr];
}else{
//是文件信息数据
[self getInfoData:lrcStr];
}
}
//进行歌词数据的重新排序
[_lrcArray
sortedArrayUsingSelector:@selector(isTimeOlderThanAnother:)];
}
getLrcData:方法的实现如下:
-(void)getLrcData:(NSString *)lrcStr{
//按照]进行分割
NSArray * arr = [lrcStr
componentsSeparatedByString:@”]”];
//解析时间 同一行歌词可能对应多个时间 后一个元素是歌词
for (int i=0; i
//去掉[号
NSString *timeStr = [arr[i]
substringFromIndex:1];
//把时间字符串转换成s为单位
NSArray * timeArr = [timeStr
componentsSeparatedByString:@”:”];
float min = [timeArr[0]
floatValue];
float sec = [timeArr[1]
floatValue];
//创建模型
LRCItem * item = [[LRCItem
alloc]init];
item.time=min*60 sec;
item.lrc = [arr lastObject];
[_lrcArray addObject:item];
}
}
getInfoData:方法的实现如下:
-(void)getInfoData:(NSString *)lrcStr{
NSArray * arr = [lrcStr
componentsSeparatedByString:@”:”];
//获取内容长度 带]符号
NSInteger len = [arr[1] length];
if ([arr[0]
isEqualToString:@”[ti”]) {
_title = [arr[1]
substringToIndex:len-1];
}else if ([arr[0]
isEqualToString:@”[ar”]){
_author = [arr[1]
substringToIndex:len-1];
}else if ([arr[0]
isEqualToString:@”[al”]){
_albume = [arr[1]
substringToIndex:len-1];
}
}
实现LRCEngine.h中声明的getCurrentLRCInLRCArray:atTime:方法如下:
-(void)getCurrentLRCInLRCArray:(void (^)(NSArray *, int))handle
atTime:(float)time{
if (!_lrcArray.count) {
handle(nil,0);
}
//找到个时间大于time的歌词位置
int index = -2;
for (int i=0; i
float lrcTime = [_lrcArray[i]
time];
if (lrcTime>time) {
index=i-1;
break;
}
}
if (index==-1) {
//条数据
index=0;
}else if (index==-2){
//没有更大的时间了 后一条数据
index=(int)_lrcArray.count-1;
}
handle(_lrcArray,index);
}
为了验证LRC歌词引擎是否正常工作,在ViewController.m文件中引入LRCEngine类的头文件并在viewDidLoad方法中编写如下代码:
– (void)viewDidLoad {
[super viewDidLoad];
LRCEngine * engine = [[LRCEngine
alloc]initWithFile:@”匆匆那年”];
[engine
getCurrentLRCInLRCArray:^(NSArray *lrcArray, int currentIndex) {
if (lrcArray) {
NSLog(@”%@n=======n%@”,lrcArray,[lrcArray[currentIndex]
lrc]);
}
} atTime:100];
}
上面的代码中获取到歌词文件《匆匆那年》第100秒时的歌词,打印结果如图5-31所示,则说明LRC歌词引擎工作顺利正常。
图5-31 LRCEngine歌词引擎的工作打印调试
5.5.3 核心播放器引擎的设计
本节将再封装一个模块作为应用的核心播放器引擎,这个引擎应该可以满足常规的音频播放需求,例如循环播放、随机播放、上一曲和下一曲等。在设计之前,先将工程设置为支持后台音频播放,其实在Xcode中设置支持后台播放的方法除了前面介绍的配置info.plist文件外,还可以通过另一种方式实现。
单击工程文件,选择其中的Capabilities项,在其中找到Background
Modes一项并将其打开,在后台运行模式中勾选支持音频后台播放的选项,过程如图5-32所示。
在工程中创建一个新的类文件,使其继承于NSObject类,将其命名为MyMusicPlayer作为核心音频播放引擎类。在MyMusicPlayer.h文件中引入如下头文件:
#import
还需要在MyMusicPlayer.h文件中声明一个协议,这个协议中约定当一个音频文件播放完之后的代理回调方法,提供给外界进行逻辑操作。代码如下:
@protocol MyMusicPlayerDelegate
-(void)musicPlayEndAndWillContinuePlaying:(BOOL)play;
@end
协议中musicPlayEndAndWillContinewPlaying:方法当一个音频播放完毕之后执行,其中传入的BOOL值参数决定是否自动播放下一个音频数据。
图5-32 设置应用程序支持后台运行模式
在MyMusicPlayer.h文件中声明如下属性与方法:
@interface MyMusicPlayer : NSObject
//歌曲名数组
@property(nonatomic,strong)NSArray * songsArray;
//对应歌曲的歌词名数组
@property(nonatomic,strong)NSArray * lrcsArray;
//是否循环播放
@property(nonatomic,assign)BOOL isRunLoop;
//是否随机播放
@property(nonatomic,assign)BOOL isRandom;
//音频播放器是否正在播放音频
@property(nonatomic,assign)BOOL isPlaying;
//代理对象
@property(nonatomic,weak)id delegate;
//获取当前播放的是第几个音频
@property(nonatomic,assign)int currentIndex;
//当前播放的音频文件的时长
@property(nonatomic,assign)int currentSongTime;
//当前播放的音频文件已经播放的时长
@property(nonatomic,assign)int hadPlayTime;
//开始播放
-(void)play;
//暂停播放
-(void)stop;
//进行继续播放与暂停播放的切换
-(void)playOrStop;
//上一曲
-(void)lastMusic;
//下一曲
-(void)nextMusic;
//停止播放
-(void)end;
//播放指定的音频文件
-(void)playAtIndex:(int)index isPlay:(BOOL)play;
@end
在前面的章节有介绍,关于音频播放的后台交互与耳机线控的操作是在AppDelegate类中进行的,因此开发者需要将MyMusicPlayer对象与程序的AppDelegate对象进行关联,便于后台交互操作的下发到具体视图控制器中,在MyMusicPlayer.m文件中引入如下头文件:
#import “AppDelegate.h”
在AppDelegate.h文件中导入MyMusicPlayer.h头文件并添加如下属性:
@property (nonatomic,strong)MyMusicPlayer *play;
在MyMusicPlayer.m文件中声明如下的内部属性:
@implementation MyMusicPlayer
{
AVAudioPlayer * _player;
NSTimer * _timer;
}
@end
在上面声明的属性中,_player用于处理音频的播放,_timer用于进行播放时间的更新。
在MyMusicPlayer.m文件中实现类的初始化方法,如下所示:
– (instancetype)init
{
self = [super init];
if (self) {
_timer = [NSTimer
scheduledTimerWithTimeInterval:1/60.0 target:self selector:@selector(update)
userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop]
addTimer:_timer forMode:NSRunLoopCommonModes];
AppDelegate * delegate
=[UIApplication sharedApplication].delegate;
delegate.play=self;
}
return self;
}
在上面的初始化方法中对定时器进行创建并将当前对象与程序的AppDelegate对象进行了关联。
实现定时器的刷新方法update如下所示:
-(void)update{
if (_player) {
_hadPlayTime = _player.currentTime;
}
}
实现在MyMusicPlayer.h文件中声明的相关方法如下所示:
//进行播放与暂停的切换
-(void)playOrStop{
//先判断是否正在播放
if (self.isPlaying) {
//已经在播放则进行停止播放操作
[self stop];
}else{
//没有在播放则进行播放操作
[self play];
}
}
-(void)play{
//判断AVAudioPlayer对象是否存在
if (_player!=nil) {
[_player play];
_isPlaying=YES;
return;
}else{
//从歌曲数组中读取个元素
NSString * path = [[NSBundle
mainBundle]pathForResource:[self.songsArray objectAtIndex:0]
ofType:@”mp3″];
NSURL * url = [NSURL
fileURLWithPath:path];
_player = [[AVAudioPlayer
alloc]initWithContentsOfURL:url error:nil];
_player.delegate=self;
[_player play];
_isPlaying=YES;
_currentIndex=0;
_currentSongTime=_player.duration;
}
}
-(void)stop{
if (_player.isPlaying) {
[_player stop];
_isPlaying=NO;
}
}
-(void)end{
[_player stop];
_isPlaying=NO;
_player=nil;
}
-(void)playAtIndex:(int)index isPlay:(BOOL)play{
[_player stop];
_isPlaying=NO;
_player = nil;
NSString * path = [[NSBundle
mainBundle]pathForResource:[self.songsArray objectAtIndex:index]
ofType:@”mp3″];
NSURL * url = [NSURL
fileURLWithPath:path];
_player = [[AVAudioPlayer
alloc]initWithContentsOfURL:url error:nil];
_player.delegate=self;
if (play) {
[_player play];
_isPlaying=YES;
}
_currentIndex=index;
_currentSongTime = _player.duration;
}
-(void)nextMusic{
BOOL play = _player.isPlaying;
[_player stop];
_isPlaying=NO;
_player=nil;
//是否是后一曲
if
(_currentIndex
_currentIndex ;
}else{
_currentIndex=0;
}
//是否随机播放
if (self.isRandom) {
unsigned long max =
self.songsArray.count;
_currentIndex = arc4random()%max;
}
NSString * path = [[NSBundle
mainBundle]pathForResource:[self.songsArray objectAtIndex:_currentIndex]
ofType:@”mp3″];
NSURL * url = [NSURL
fileURLWithPath:path];
_player = [[AVAudioPlayer
alloc]initWithContentsOfURL:url error:nil];
_currentSongTime=_player.duration;
_player.delegate=self;
if (play) {
[_player play];
_isPlaying=YES;
}
}
-(void)lastMusic{
BOOL play = _player.isPlaying;
[_player stop];
_isPlaying=NO;
_player=nil;
if (_currentIndex>0) {
_currentIndex–;
}else{
_currentIndex=(int)_songsArray.count-1;
}
if (self.isRandom) {
unsigned long max =
self.songsArray.count;
_currentIndex = arc4random()%max;
}
NSString * path = [[NSBundle
mainBundle]pathForResource:[self.songsArray objectAtIndex:_currentIndex]
ofType:@”mp3″];
NSURL * url = [NSURL
fileURLWithPath:path];
_player = [[AVAudioPlayer alloc]initWithContentsOfURL:url
error:nil];
_currentSongTime=_player.duration;
_player.delegate=self;
if (play) {
[_player play];
_isPlaying=YES;
}
}
在MyMusicPlayer.m文件中还需要实现一个AVAudioPlayerDelegate协议中约定的方法:audioPlayerDidFinishPlaying:successfully:方法,这个方法在AVAudioPlayer播放结束后会被调用,实现方法如下:
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player
successfully:(BOOL)flag{
_player = nil;
_isPlaying=NO;
//是否循环播放
if (_isRandom) {
unsigned long max =
self.songsArray.count;
int songIndex = arc4random()%max;
NSString * path = [[NSBundle
mainBundle]pathForResource:[self.songsArray objectAtIndex:songIndex]
ofType:@”mp3″];
NSURL * url = [NSURL
fileURLWithPath:path];
_player = [[AVAudioPlayer alloc]initWithContentsOfURL:url
error:nil];
_player.delegate=self;
[_player play];
_isPlaying=YES;
[self.delegate
musicPlayEndAndWillContinuePlaying:YES];
_currentIndex=songIndex;
_currentSongTime=_player.duration;
return;
}
if
(_currentIndex
//是否是后一首
NSString * path = [[NSBundle
mainBundle]pathForResource:[self.songsArray objectAtIndex: _currentIndex]
ofType:@”mp3″];
NSURL * url = [NSURL fileURLWithPath:path];
_player = [[AVAudioPlayer
alloc]initWithContentsOfURL:url error:nil];
_currentSongTime=_player.duration;
_player.delegate=self;
[_player play];
_isPlaying=YES;
[self.delegate musicPlayEndAndWillContinuePlaying:YES];
}else if
(_currentIndex==self.songsArray.count-1){
//是否循环
if (_isRunLoop) {
_currentIndex=0;
NSString * path = [[NSBundle
mainBundle]pathForResource:[self.songsArray objectAtIndex:_currentIndex]
ofType:@”mp3″];
NSURL * url = [NSURL
fileURLWithPath:path];
_player = [[AVAudioPlayer
alloc]initWithContentsOfURL:url error:nil];
_player.delegate=self;
_currentSongTime=_player.duration;
[_player play];
_isPlaying=YES;
[self.delegate
musicPlayEndAndWillContinuePlaying:YES];
}else{
[self.delegate
musicPlayEndAndWillContinuePlaying:NO];
}
}
}
5.5.4 歌曲列表与歌词显示视图界面的设计
前面小节中设计的两个模块:歌词引擎模块和播放引擎模块都属于工具类模块,本节将设计一个视图类模块,歌曲列表与歌词显示视图应支持左右滑动进行界面模式的切换,左边模式为歌曲列表与单行歌词显示控件,右边模式为多行歌词显示控件。
在工程中创建一个新的类文件,取名为MusicContentView,使其继承于UIView类。因为MusicContentView中包含歌曲列表,单击歌曲列表中的歌曲应该支持播放歌曲的切换。因此MusicContentView中应该能够操作前面设计的音频播放引擎对象,在MusicContentView.h文件中引入如下头文件并声明如下属性和方法:
#import “MyMusicPlayer.h”
@interface MusicContentView : UIView
//歌曲列表数据源数组
@property(nonatomic,strong)NSArray * titleDataAttay;
//这个方法设置当前界面显示的歌词 对应歌曲播放的相应时间
-(void)setCurretLRCArray:(NSArray *)array index:(int)index;
//播放器引擎对象的引用
@property(nonatomic,strong)MyMusicPlayer * play;
//锁屏界面要显示的图片
@property(nonatomic,readonly)UIImage * lrcImage;
@end
前面小节中介绍过,iOS锁屏界面只支持显示图片,若想在其中显示滚动的歌词,开发者需要不停的刷新锁屏界面的图片,上面的lrcImage属性为播放歌曲当前时刻对应的歌词图片对象。
在MusicContentView.m文件中引入如下头文件:
#import “LRCItem.h”
歌曲列表可以采用UITableView来设计,在MusicContentView.m文件中添加遵守相应协议的代码。
@interface MusicContentView()
@end
在MusicContentView.m文件中声明如下属性:
@implementation MusicContentView
{
UIScrollView * _scrollView;
//歌曲列表格视图
UITableView * _titleTableView;
//单行显示的歌词显示标签
UILabel * _lrcLabel;
//锁屏图片中的歌词标签
UILabel * _lrcIMGLabel;
//锁屏图片的背景
UIImageView * _lrcIMGbg;
//多行显示的歌词显示标签
UILabel * _lrcView;
//多行显示歌词视图的显示行数
int _lines;
}
@end
在MusicContentView.m文件中实现类的初始化方法,如下所示:
– (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
//设置视图背景为透明色
self.backgroundColor = [UIColor
clearColor];
//初始化滚动视图
_scrollView = [[UIScrollView
alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
[self addSubview:_scrollView];
_scrollView.backgroundColor =
[UIColor clearColor];
//初始化歌曲列表
_titleTableView = [[UITableView
alloc]initWithFrame:CGRectMake(40,0,
frame.size.width-90, frame.size.height-40) style:UITableViewStylePlain];
_titleTableView.backgroundColor =
[UIColor clearColor];
_titleTableView.delegate=self;
_titleTableView.dataSource=self;
//设置表格视图行间无分割线
_titleTableView.separatorStyle =
UITableViewCellSeparatorStyleNone;
[_scrollView
addSubview:_titleTableView];
//设置滚地视图的可滚动范围
_scrollView.contentSize =
CGSizeMake(frame.size.width*2, frame.size.height);
_scrollView.showsHorizontalScrollIndicator=NO;
//设置滚动视图翻页效果
_scrollView.pagingEnabled=YES;
//初始化单行显示的歌词控件
_lrcLabel = [[UILabel
alloc]initWithFrame:CGRectMake(20, frame.size.height-50, frame.size.width-40,
50)];
_lrcLabel.backgroundColor =
[UIColor clearColor];
//设置歌词颜色为白色
_lrcLabel.textColor = [UIColor whiteColor];
[_scrollView
addSubview:_lrcLabel];
_lrcLabel.textAlignment =
NSTextAlignmentCenter;
_lrcLabel.numberOfLines=0;
//初始化多行显示的歌词控件
_lrcView = [[UILabel
alloc]initWithFrame:CGRectMake(frame.size.width 20, 50, frame.size.width-40,
frame.size.height-100)];
//根据屏幕尺寸获取显示行数
_lines =
(int)_lrcView.frame.size.height/21;
_lrcView.numberOfLines = _lines;
_lrcView.textAlignment =
NSTextAlignmentCenter;
_lrcView.textColor = [UIColor whiteColor];
[_scrollView
addSubview:_lrcView];
//初始化锁屏图片上的歌词标签
_lrcIMGLabel = [[UILabel
alloc]initWithFrame:CGRectMake(20, 0, self.frame.size.width-40,
self.frame.size.height)];
_lrcIMGLabel.numberOfLines =
_lines;
_lrcIMGLabel.textAlignment =
NSTextAlignmentCenter;
_lrcIMGLabel.textColor = [UIColor
whiteColor];
}
return self;
}
在MusicContentView.m中重写实现titleDataArray数据源的setter方法,如下所示:
-(void)setTitleDataAttay:(NSArray *)titleDataAttay{
_titleDataAttay = [NSArray
arrayWithArray:titleDataAttay];
[_titleTableView reloadData];
}
在MusicContentView.m中实现UITableView控件的代理与数据源协议中的相应方法如下所示:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
//设置分区数为1
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section{
//设置行数为数据源中数据个数
return self.titleDataAttay.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell * cell = [tableView
dequeueReusableCellWithIdentifier:@”cellId”];
if (cell==nil) {
cell = [[UITableViewCell
alloc]initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:@”cellId”];
cell.backgroundColor = [UIColor
clearColor];
cell.textLabel.textColor =
[UIColor whiteColor];
//设置cell的选中效果为无
cell.selectionStyle =
UITableViewCellSelectionStyleNone;
}
cell.textLabel.text =
self.titleDataAttay[indexPath.row];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
*)indexPath{
//单击歌曲列表中某行后播放相应的歌曲
[self.play
playAtIndex:(int)indexPath.row isPlay:self.play.isPlaying];
}
在MusicContentView.m中实现setCurrentLRCArray:index:方法如下所示:
-(void)setCurretLRCArray:(NSArray *)array index:(int)index{
NSString * lineLRC = [(LRCItem
*)array[index] lrc];
_lrcLabel.text = lineLRC;
//进行行数设置
NSMutableString * lrcStr =
[[NSMutableString alloc]init];
if (index
//前面用n补齐
int offset =
(int)_lines/2-index;
for (int j=0; j
[lrcStr
appendFormat:@”n”];
}
for (int j=0;
j
[lrcStr appendFormat:@”%@n”,[(LRCItem
*)array[j] lrc]];
}
} else if
(array.count-1-index
//后面用n补齐
int offset =
(int)_lines/2-(int)(array.count-index-1);
for (int j=index-(_lines/2);
j
[lrcStr
appendFormat:@”%@n”,[(LRCItem *)array[j] lrc]];
}
for (int j=0; j
[lrcStr
appendFormat:@”n”];
}
}else {
for (int j=0; j
[lrcStr appendString:[(LRCItem
*)array[index-_lines/2 j] lrc]];
[lrcStr
appendString:@”n”];
}
}
NSMutableAttributedString * attriStr
= [[NSMutableAttributedString alloc]initWithString:lrcStr];
NSRange range = [lrcStr
rangeOfString:[array[index] lrc]];
[attriStr
setAttributes:@{NSForegroundColorAttributeName:[UIColor greenColor]}
range:range];
_lrcView.attributedText = attriStr;
_lrcIMGLabel.attributedText =
attriStr;
//进行截屏
if (!_lrcIMGbg) {
_lrcIMGbg = [[UIImageView
alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width,
self.frame.size.height)];
_lrcIMGbg.image = [UIImage
imageNamed:@”BG.jpeg”];
[_lrcIMGbg
addSubview:_lrcIMGLabel];
}
UIGraphicsBeginImageContext(_lrcIMGbg.frame.size);
CGContextRef context =
UIGraphicsGetCurrentContext();
[_lrcIMGbg.layer
renderInContext:context];
UIImage *img =
UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
_lrcImage = [img copy];
}
5.5.5 播放器主页面的实现
前面小节中所做的工具类模块与视图类模块都是独立的完成某些功能或者显示某种视图,本小节将在ViewController类中将其进行组合与协调,完成音频播放器的开发。
在ViewController.m文件中引入如下头文件:
#import “LRCEngine.h”
#import “MyMusicPlayer.h”
#import “MusicContentView.h”
#import
在ViewController.m中编写遵守相关协议的代码并声明一些属性如下所示:
@interface ViewController ()
{
MyMusicPlayer * _player;
//内容视图
MusicContentView * _contentView;
//标题标签
UILabel * _titleLabel;
//进度条
UIProgressView * _progress;
//播放按钮
UIButton * _playBtn;
//下一曲按钮
UIButton * _nextBtn;
//上一曲按钮
UIButton * _lastBtn;
//循环播放按钮
UIButton * _circleBtn;
//随机播放按钮
UIButton * _randomBtn;
//存放歌曲名
NSArray * _dataArray;
NSTimer * _timer;
}
@end
在ViewController.m文件的viewDidLoad方法中调用如下方法:
– (void)viewDidLoad {
[super viewDidLoad];
//创建数据
[self creatData];
//创建播放模块
[self creatPlayer];
//创建视图模块
[self creatView];
//进行刷新UI操作
[self updateUI];
}
creatData方法的实现如下所示:
-(void)creatData{
_dataArray = @[@”匆匆那年”,@”致青春”,@”清风徐来”,@”矜持”,@”暗涌”,@”天空”,@”容易受伤的女人”,@”清平调”,@”但愿人长久”,@”暧昧”,@”执迷不悔”,@”约定”,@”我愿意”,@”棋子”,@”梦醒了”,@”影子”,@”人间”,@”爱与痛的边缘”,@”旋木”,@”红豆”,@”传奇”,@”爱不可及”];
}
creatData方法对数据源进行了创建,前提是要将上面数组中歌名对应的音频文件和歌词文件都导入项目中,歌词文件要与音频文件名称对应,为了使工程结构看起来更整洁,开发者可以将歌曲与歌词文件分别放于相应的目录下,如图5-33所示。
图5-33 Xcode的工程目录结构
creatPlayer方法的实现如下:
-(void)creatPlayer{
_player = [[MyMusicPlayer
alloc]init];
_player.songsArray=_dataArray;
NSMutableArray * mulArr =
[[NSMutableArray alloc]init];
for (int i=0; i
//进行歌词模块创建
LRCEngine * engine = [[LRCEngine
alloc]initWithFile:_dataArray[i]];
[mulArr addObject:engine];
}
_player.lrcsArray = mulArr;
_player.delegate=self;
}
creatView方法的实现如下:
-(void)creatView{
//创建背景
UIImageView * bg = [[UIImageView
alloc]initWithFrame:self.view.bounds];
bg.image = [UIImage imageNamed:@”BG.jpeg”];
//设置为可接收用户交互
bg.userInteractionEnabled=YES;
[self.view addSubview:bg];
//创建歌曲标题Label
_titleLabel = [[UILabel
alloc]initWithFrame:CGRectMake(0, 20, bg.frame.size.width, 40)];
_titleLabel.font = [UIFont
boldSystemFontOfSize:22];
_titleLabel.textAlignment =
NSTextAlignmentCenter;
_titleLabel.text = _dataArray[0];
_titleLabel.backgroundColor =
[UIColor clearColor];
_titleLabel.textColor = [UIColor
whiteColor];
[bg addSubview:_titleLabel];
//创建歌曲进度条
_progress = [[UIProgressView
alloc]initWithProgressViewStyle:UIProgressViewStyleDefault];
_progress.progressTintColor=[UIColor
redColor];
_progress.trackTintColor = [UIColor
whiteColor];
_progress.frame=CGRectMake(20,
self.view.frame.size.height-70, self.view.frame.size.width-40, 5);
[bg addSubview:_progress];
//创建播放按钮
_playBtn = [UIButton
buttonWithType:UIButtonTypeCustom];
[_playBtn setBackgroundImage:[UIImage
imageNamed:@”play”] forState:UIControlStateNormal];
[_playBtn setBackgroundImage:[UIImage
imageNamed:@”pause”] forState:UIControlStateSelected];
_playBtn.frame=CGRectMake(self.view.frame.size.width/2-20,
self.view.frame.size.height-45, 40, 30);
[_playBtn addTarget:self
action:@selector(playMusic) forControlEvents:UIControlEventTouchUpInside];
[bg addSubview:_playBtn];
//创建下一曲按钮
_nextBtn = [UIButton
buttonWithType:UIButtonTypeCustom];
_nextBtn.frame=CGRectMake(self.view.frame.size.width/2 40,
self.view.frame.size.height-45, 40, 30);
[_nextBtn setBackgroundImage:[UIImage
imageNamed:@”nextMusic”] forState:UIControlStateNormal];
[_nextBtn addTarget:self
action:@selector(next) forControlEvents:UIControlEventTouchUpInside];
[bg addSubview:_nextBtn];
//创建上一曲按钮
_lastBtn = [UIButton
buttonWithType:UIButtonTypeCustom];
_lastBtn.frame =
CGRectMake(self.view.frame.size.width/2-80, self.view.frame.size.height-45, 40,
30);
[_lastBtn setBackgroundImage:[UIImage
imageNamed:@”aboveMusic”] forState:UIControlStateNormal];
[_lastBtn addTarget:self
action:@selector(last) forControlEvents:UIControlEventTouchUpInside];
[bg addSubview:_lastBtn];
//创建循环播放按钮
_circleBtn = [UIButton
buttonWithType:UIButtonTypeCustom];
_circleBtn.frame =
CGRectMake(self.view.frame.size.width/2-140, self.view.frame.size.height-45,
40, 30);
[_circleBtn
setBackgroundImage:[UIImage imageNamed:@”circleClose”]
forState:UIControlStateNormal];
[_circleBtn
setBackgroundImage:[UIImage imageNamed:@”circleOpen”]
forState:UIControlStateSelected];
[_circleBtn addTarget:self
action:@selector(circle) forControlEvents:UIControlEventTouchUpInside];
[bg addSubview:_circleBtn];
//创建随机播放按钮
_randomBtn = [UIButton
buttonWithType:UIButtonTypeCustom];
_randomBtn.frame=CGRectMake(self.view.frame.size.width/2 100,
self.view.frame.size.height-45, 40, 30);
[_randomBtn
setBackgroundImage:[UIImage imageNamed:@”randomClose”]
forState:UIControlStateNormal];
[_randomBtn
setBackgroundImage:[UIImage imageNamed:@”randomOpen”]
forState:UIControlStateSelected];
[_randomBtn addTarget:self
action:@selector(random) forControlEvents:UIControlEventTouchUpInside];
[bg addSubview:_randomBtn];
//创建歌曲列表与歌词显示控件视图
_contentView = [[MusicContentView
alloc]initWithFrame:CGRectMake(0, 70, self.view.frame.size.width,
self.view.frame.size.height-150)];
_contentView.titleDataAttay =
_dataArray;
_contentView.play=_player;
[bg addSubview:_contentView];
}
各个功能按钮的触发方法的实现如下:
-(void)playMusic{
if (_player.isPlaying) {
_playBtn.selected=NO;
[_player stop];
}else{
_playBtn.selected=YES;
[_player play];
}
}
-(void)next{
[_player nextMusic];
}
-(void)last{
[_player lastMusic];
}
-(void)circle{
if (_player.isRunLoop) {
_player.isRunLoop=NO;
_circleBtn.selected=NO;
}else{
_player.isRunLoop=YES;
_circleBtn.selected=YES;
}
}
-(void)random{
if (_player.isRandom) {
_player.isRandom=NO;
_randomBtn.selected=NO;
}else{
_player.isRandom=YES;
_randomBtn.selected=YES;
}
}
updateUI方法的实现如下:
-(void)updateUI{
_timer = [NSTimer
scheduledTimerWithTimeInterval:1/60.0 target:self selector:@selector(update)
userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop]
addTimer:_timer forMode:NSRunLoopCommonModes];
}
定时器的触发方法update的实现如下:
-(void)update{
_titleLabel.text =
_dataArray[[_player currentIndex]];
//更新进度条
if (_player.hadPlayTime!=0) {
float progress =
(float)_player.hadPlayTime/_player.currentSongTime;
_progress.progress = progress;
}
//更新歌词
LRCEngine * engine =
_player.lrcsArray[_player.currentIndex];
[engine
getCurrentLRCInLRCArray:^(NSArray *lrcArray, int currentIndex) {
[_contentView setCurretLRCArray:lrcArray
index:currentIndex];
} atTime:_player.hadPlayTime];
//更新锁屏界面
NSMutableDictionary *dict =
[[NSMutableDictionary alloc] init];
[dict
setObject:_dataArray[_player.currentIndex] forKey:MPMediaItemPropertyTitle];
[dict setObject:@”王菲” forKey:MPMediaItemPropertyArtist];
[dict setObject:@”致敬天后” forKey:MPMediaItemPropertyAlbumTitle];
UIImage *newImage =
_contentView.lrcImage;
[dict setObject:[[MPMediaItemArtwork
alloc] initWithImage:newImage]
forKey:MPMediaItemPropertyArtwork];
[dict setObject:[NSNumber
numberWithDouble:_player.currentSongTime]
forKey:MPMediaItemPropertyPlaybackDuration];
[dict setObject:[NSNumber
numberWithDouble:_player.hadPlayTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
//音乐当前已经过时间
[[MPNowPlayingInfoCenter
defaultCenter] setNowPlayingInfo:dict];
}
在ViewController.m中还需要实现一首歌曲播放完毕后调用的协议方法,如下所示:
-(void)musicPlayEndAndWillContinuePlaying:(BOOL)play{
if (play) {
_playBtn.selected=YES;
}else{
_playBtn.selected=NO;
}
}
5.5.6 后台播放音频用户交互的处理
后台播放音频的用户交互是通过系统与AppDalegate类对象实现的,在AppDelegate.m文件的application:didFinishLaunchingWithOptions:方法中添加如下代码:
– (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
AVAudioSession *session =
[AVAudioSession sharedInstance];
[session setActive:YES error:nil];
[session
setCategory:AVAudioSessionCategoryPlayback error:nil];
[[UIApplication sharedApplication]
beginReceivingRemoteControlEvents];
return YES;
}
在AppDelegate.m中实现接收交互通知的方法如下:
//后台播放控制
-(void)remoteControlReceivedWithEvent:(UIEvent *)event{
if
(event.type==UIEventTypeRemoteControl) {
switch (event.subtype) {
case
UIEventSubtypeRemoteControlPlay:
[self.play play];
break;
case
UIEventSubtypeRemoteControlNextTrack:
[self.play nextMusic];
break;
case
UIEventSubtypeRemoteControlPreviousTrack:
[self.play lastMusic];
break;
case
UIEventSubtypeRemoteControlPause:
[self.play stop];
break;
case
UIEventSubtypeRemoteControlTogglePlayPause:
[self.play playOrStop];
break;
default:
break;
}
}
}
至此,《天后王菲》音频播放器应用就已经开发完成了,其中主要界面如图5-34~图5-37所示。
图5-34 歌曲列表界面 图5-35 多行歌词显示界面
图5-36 后台播放上拉抽屉界面 图5-37 后台播放锁屏界面
学习之余,读者可以使用这款小应用听听音乐,放松一下,本章后,向歌坛天后王菲致以敬意
评论
还没有评论。