使用Swift构建一个视频时间轴控件

本文详细介绍了如何使用Swift构建一个视频时间轴控件,用于视频截取。内容涵盖控件设计、界面搭建、数据填充、交互添加等步骤,涉及UIKit、AVFoundation、Photos等技术。通过自定义控件,实现视频缩略图的显示、滑块交互和实时状态更新等功能。
摘要由CSDN通过智能技术生成

##关键词


控件 属性 VideoLine 扩展 逻辑 cgImage 访问 设计 自定义 交互

本文所有示例代码或Demo可以在此获取:https://github.com/WillieWangWei/VideoLine.git

如果本文对你有所帮助,请给个Star?

##概述


界面控件是所iOS程序重要的组成部分,用户可以通过它们与应用程序进行交互。苹果提供了一套强大的控件组来满足日常的开发需求,我们可以使用这些控件来搭建大部分的用户界面。
但是当我们需要实现一些特别的场景时,这些控件就无法满足需求。此时我们可以基于系统控件来编写自定义控件,比如以下场景中底部的选择器:
视频时间轴选择

本文从实际开发的角度出发,讲解一个控件从无到有的过程,是一篇综合性比较强的教程,主要涉及以下技术点:

  • UIKit
  • AVFoundation
  • Photos
  • SnapKit
  • Access Control
  • extension

目录:

  • 分析需求
  • 拆分控件
  • 搭建界面
  • 填充数据
  • 添加交互
  • 设计API
  • 代码优化

##分析需求


这是一个常见的场景——当用户选择了一个本地视频后,在此界面预览视频并对其长度进行裁剪,最终得到符合业务要求的短视频。
暂时忽略上部视频的预览区域,我们需要实底部的“缩略图进度条”。观察后我们发现这个控件有以下几个特点:

  1. 对视频片段进行采样,生成缩略图排列,且可以左右滑动。
  2. 中间有一个选择区域,可以通过滑动左右两边的滑块来确定选中区域的大小。
  3. 左右滑块滑动时会出现一个边框,表示滑动的边界。
  4. 选择区域以外的内容有黑色半透明蒙版。
  5. 选择区域中有一条指示线指示当前播放进度。
  6. 有文字说明当前选择片段的开始时间、总共时长以及结束时间。
  7. 与上方播放器实时联动。

初步的分析让我们对需要实现的内容有了大致的了解,但通常会忽略很多细节,这会在实际编码中体现出来。

##拆分控件


现在需要初步确定各个位置用什么系统控件来实现。这里考虑的越周全,实际编码时绕的弯路就越少,我们结合截图来分析:
拆分控件

123用来显示当前选择区域的状态,不接收点击事件,所以直接使用UILabel

7区域支持左右滑动,首先考虑UIScrollView。其承载了多个尺寸相同的缩略图且横向滑动,那么使用拥有重用机制的UICollectionView最合适。

6看起来是一个白色的方框,左右两边均可拖动,系统并未提供类似的控件,所以要对其再次进行拆分。
由于左右边框(滑块)都可以单独拖动,所以判断使用两个单独的UIView,并各自绑定不同的拖拽手势。为了方便的使用自定义图片,确定滑块使用UIImageView。上下的边框也分解为两个单独的UIView,添加约束使其前后与左右边框相接即可。如图:
选择区域框拆分

5又是一个边框,但是它的大小的固定的,用来表示6的可选范围,所以可以直接使用UIView,设置其layer的相关属性即可得到所需样式。

48是选择区域之外的黑色蒙版,它的边界随着相邻滑块的位置而变化。可以直接使用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!
}
  • 这里没有将48黑色蒙版声明为全局变量,因为它们一旦被创建和添加约束后,后续不会再进行修改。更多关于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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值