##关键词
控件 属性 VideoLine 扩展 逻辑 cgImage 访问 设计 自定义 交互
本文所有示例代码或Demo可以在此获取:https://github.com/WillieWangWei/VideoLine.git
如果本文对你有所帮助,请给个Star?
##概述
界面控件是所iOS程序重要的组成部分,用户可以通过它们与应用程序进行交互。苹果提供了一套强大的控件组来满足日常的开发需求,我们可以使用这些控件来搭建大部分的用户界面。
但是当我们需要实现一些特别的场景时,这些控件就无法满足需求。此时我们可以基于系统控件来编写自定义控件,比如以下场景中底部的选择器:
本文从实际开发的角度出发,讲解一个控件从无到有的过程,是一篇综合性比较强的教程,主要涉及以下技术点:
- UIKit
- AVFoundation
- Photos
- SnapKit
- Access Control
- extension
目录:
- 分析需求
- 拆分控件
- 搭建界面
- 填充数据
- 添加交互
- 设计API
- 代码优化
##分析需求
这是一个常见的场景——当用户选择了一个本地视频后,在此界面预览视频并对其长度进行裁剪,最终得到符合业务要求的短视频。
暂时忽略上部视频的预览区域,我们需要实底部的“缩略图进度条”。观察后我们发现这个控件有以下几个特点:
- 对视频片段进行采样,生成缩略图排列,且可以左右滑动。
- 中间有一个选择区域,可以通过滑动左右两边的滑块来确定选中区域的大小。
- 左右滑块滑动时会出现一个边框,表示滑动的边界。
- 选择区域以外的内容有黑色半透明蒙版。
- 选择区域中有一条指示线指示当前播放进度。
- 有文字说明当前选择片段的开始时间、总共时长以及结束时间。
- 与上方播放器实时联动。
初步的分析让我们对需要实现的内容有了大致的了解,但通常会忽略很多细节,这会在实际编码中体现出来。
##拆分控件
现在需要初步确定各个位置用什么系统控件来实现。这里考虑的越周全,实际编码时绕的弯路就越少,我们结合截图来分析:
1、2、3用来显示当前选择区域的状态,不接收点击事件,所以直接使用UILabel
。
7区域支持左右滑动,首先考虑UIScrollView
。其承载了多个尺寸相同的缩略图且横向滑动,那么使用拥有重用机制的UICollectionView
最合适。
6看起来是一个白色的方框,左右两边均可拖动,系统并未提供类似的控件,所以要对其再次进行拆分。
由于左右边框(滑块)都可以单独拖动,所以判断使用两个单独的UIView
,并各自绑定不同的拖拽手势。为了方便的使用自定义图片,确定滑块使用UIImageView
。上下的边框也分解为两个单独的UIView
,添加约束使其前后与左右边框相接即可。如图:
5又是一个边框,但是它的大小的固定的,用来表示6的可选范围,所以可以直接使用UIView
,设置其layer
的相关属性即可得到所需样式。
4、8是选择区域之外的黑色蒙版,它的边界随着相邻滑块的位置而变化。可以直接使用UIView
,并添加约束使其与相邻滑块相接。
整个控件在z方向(也就是遮盖关系)的层级为6 > 5 > 4 = 8 > 7 = 1 = 2 = 3。
##搭建界面
新建一个Swift
文件,创建一个类VideoLine
,继承自UIView
。
class VideoLine: UIView {
}
给这个类添加拆分后必要的子控件。
class VideoLine: UIView {
/// 左滑块
var leftSlider: UIImageView!
/// 右滑块
var rightSlider: UIImageView!
/// 开始时间label
var startTimeLabel: UILabel!
/// 结束时间label
var endTimeLabel: UILabel!
/// 总计时间label
var durationTimeLabel: UILabel!
/// 下方呈现所有缩略图并可以滚动的view
var collectionView: UICollectionView!
/// 拖动滑块时出现的边界
var limitBoard: UIView!
/// 播放进度指示器
var indicator: UIView!
}
- 这里没有将4、8黑色蒙版声明为全局变量,因为它们一旦被创建和添加约束后,后续不会再进行修改。更多关于Swift中的变量,请看这里。
- 属性全部使用自动解包的可选类型,表示我们将在后续对所有对象进行初始化,并可以直接对其解包使用。更多关于可选类型,请看这里。
声明一个方法,对所有属性进行初始化。
// 初始化所有视图
func setupUtil() {
startTimeLabel = UILabel()
startTimeLabel.text = "开始时间"
self.addSubview(startTimeLabel)
startTimeLabel.snp.makeConstraints { (make) in
make.leading.equalTo(8)
make.top.equalTo(self)
}
endTimeLabel = UILabel()
endTimeLabel.text = "结束时间"
self.addSubview(endTimeLabel)
endTimeLabel.snp.makeConstraints { (make) in
make.trailing.equalTo(-8)
make.top.equalTo(self)
}
durationTimeLabel = UILabel()
durationTimeLabel.text = "总共时间"
self.addSubview(durationTimeLabel)
durationTimeLabel.snp.makeConstraints { (make) in
make.centerX.top.equalTo(self)
}
let flowLayout = UICollectionViewFlowLayout()
flowLayout.itemSize = thumbnailSize
flowLayout.minimumLineSpacing = 0
flowLayout.minimumInteritemSpacing = 0
flowLayout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: flowLayout)
collectionView.bounces = false
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
collectionView.contentInset = UIEdgeInsetsMake(0, CGFloat(margin), 0, CGFloat(margin))
collectionView.showsHorizontalScrollIndicator = false
collectionView.dataSource = self
collectionView.delegate = self
collectionView.backgroundColor = UIColor.orange
self.addSubview(collectionView)
collectionView.snp.makeConstraints { (make) in
make.leading.trailing.bottom.equalTo(self)
make.height.equalTo(thumbnailSize.height)
}
leftSlider = UIImageView()
leftSlider.backgroundColor = UIColor.white
leftSlider.isUserInteractionEnabled = true
leftSlider.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(leftSliderPaning)))
self.addSubview(leftSlider)
leftSlider.snp.makeConstraints { (make) in
make.leading.equalTo(margin)
make.bottom.equalTo(collectionView)
make.size.equalTo(CGSize(width: 10, height: thumbnailSize.height))
}
let leftMask = UIView()
leftMask.isUserInteractionEnabled = false
leftMask.backgroundColor = UIColor(white: 0, alpha: 0.7)
self.addSubview(leftMask)
leftMask.snp.makeConstraints { (make) in
make.leading.top.bottom.equalTo(collectionView)
make.trailing.equalTo(leftSlider.snp.leading)
}
rightSlider = UIImageView()
rightSlider.backgroundColor = UIColor.white
rightSlider.isUserInteractionEnabled