深入解析 iOS 视频录制(一):录制管理核心MWRecordingController 类的设计与实现
深入解析iOS视频录制(二):自定义UI的实现
深入解析 iOS 视频录制(三):完整录制流程的实现与整合
引言
在上一篇博客中,我们深入解析了 iOS 视频录制功能的核心类 MWRecordingController 的实现,涵盖了如何管理视频会话、设置输入输出、控制摄像头以及录制的启动与停止等重要功能。本篇博客将接着上文,探索自定义 UI 的实现,重点讲解如何通过 MWRecordingPreview 预览视图、MWRcordingControlView 控制视图以及精心设置的自定义按钮来提升视频录制的用户体验。
我们将逐步揭开这些自定义 UI 组件背后的设计与实现,了解如何通过灵活的视图布局与动画效果,让录制过程更加直观与流畅。希望通过本篇博客,能够帮助大家更深入地理解如何在 iOS 中定制出高质量的视频录制界面。
UI实现
在视频录制功能中,UI 设计不仅仅是视觉呈现,更是与用户交互的桥梁。一个直观、流畅且具有良好反馈的界面,能够大大提升用户的使用体验。通过自定义 UI,我们能够精细控制各个交互细节,确保用户能够快速而顺畅地完成录制操作。
本篇博客将从三个核心部分入手,详细讲解自定义 UI 的实现:
- 预览视图的实现:如何设计并展示视频录制的预览内容,确保录制时用户能够实时看到画面。
- 控制视图的实现:包括录制、暂停、重新录制以及完成按钮等,通过合理布局和状态管理,使得控制操作清晰易懂。
- 顶部导航栏视图的实现:导航栏中的返回按钮与切换摄像头按钮如何与视频录制功能进行无缝集成。
接下来,我们将逐一展开讲解这些关键视图的设计与实现,带大家了解如何通过代码构建一个高效且美观的视频录制界面。
MWRecordingPreview:预览视图的实现
MWRecordingPreview 类是整个视频录制界面中关键部分之一,它负责显示来自摄像头的实时视频预览。在本次实现中,我们简化了设计,确保预览层与录制会话紧密配合,并通过简单的配置来实现画面显示。
以下是 MWRecordingPreview 类的实现代码:
import UIKit
import AVFoundation
class MWRecordingPreview: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
previewLayer.videoGravity = .resizeAspectFill
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override class var layerClass: AnyClass {
return AVCaptureVideoPreviewLayer.self
}
/// 预览图层
var previewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
/// 设置图层会话
var session:AVCaptureSession? {
didSet {
previewLayer.session = session
}
}
}
- layerClass:通过重写 layerClass 方法,我们指定了该视图的图层类型为 AVCaptureVideoPreviewLayer,这是一个专门用于显示摄像头预览内容的图层类。
- previewLayer:该属性返回类型为A VCaptureVideoPreviewLayer 的图层,我们可以通过它来访问与摄像头相关的配置。
- session:这个属性用来设置 AVCaptureSession,它是视频录制功能的核心,管理着输入输出设备以及数据流。通过 didSet 方法,当会话对象被设置时,预览层的 session 会自动与之绑定,从而实现实时预览功能。
通过这种简单的实现,我们就能将摄像头的实时图像呈现在自定义视图 MWRecordingPreview 中,并且具备了灵活的图像填充方式,使用 .resizeAspectFill 可以确保预览画面根据视图的大小自适应显示。
MWRecordingControlView:控制视图的实现
MWRecordingControlView 类负责管理视频录制过程汇总的交互空间,包括录制按钮、录制时间显示、重新录制按钮以及完成按钮。通过合理布局与状态管理,确保用户能够清晰地了解录制进度,并灵活地控制录制行为。
以下是 MWRecordingControlView 类的实现代码:
import UIKit
enum MWRecordingControlState {
/// 正常
case normal
/// 录制中
case recording
/// 录制完成
case finish
}
class MWRecordingControlView: UIView {
/// 录制按钮
private let recordButton = MWRecordingButton()
/// 录制时间
private let recordTimeLabel = UILabel()
/// 重新录制按钮
private let reRecordButton = MWRecordingItemButton()
/// 完成按钮
private let finishButton = MWRecordingItemButton()
/// 录制按钮点击事件回调
var recordButtonClickBlock: (() -> Void)?
/// 重新录制点击回调
var reRecordButtonClickBlock: (() -> Void)?
/// 完成点击回调
var finishButtonClickBlock: (() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
setLayout()
setEvent()
setRecordingState(state: .normal)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
// 录制按钮
addSubview(recordButton)
// 录制时间
addSubview(recordTimeLabel)
recordTimeLabel.textColor = .white
recordTimeLabel.font = MWFontHelper.font(name: .nunito, size: 14, weight: .bold)
// 重新录制按钮
addSubview(reRecordButton)
reRecordButton.setImage(UIImage(named: "recording_retake"), for: .normal)
reRecordButton.setTitle(MWLocaleStringHelper.getString("Retake"), for: .normal)
reRecordButton.setTitleColor(.white, for: .normal)
reRecordButton.titleLabel?.font = MWFontHelper.font(name: .nunito, size: 14, weight: .bold)
reRecordButton.titleLabel?.textAlignment = .center
// 完成按钮
addSubview(finishButton)
finishButton.setImage(UIImage(named: "recording_complete"), for: .normal)
finishButton.setTitle(MWLocaleStringHelper.getString("Complete"), for: .normal)
finishButton.setTitleColor(.white, for: .normal)
finishButton.titleLabel?.font = MWFontHelper.font(name: .nunito, size: 14, weight: .bold)
finishButton.titleLabel?.textAlignment = .center
}
private func setLayout() {
// 录制按钮
recordButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview()
make.width.height.equalTo(72.0)
}
// 录制时间
recordTimeLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(recordButton.snp.bottom).offset(12.0)
make.height.equalTo(19.0)
}
// 重新录制按钮
reRecordButton.snp.makeConstraints { make in
make.trailing.equalTo(recordButton.snp.leading).offset(-44.0)
make.width.equalTo(64.0)
make.height.equalTo(63.0)
make.centerY.equalTo(recordButton)
}
// 完成按钮
finishButton.snp.makeConstraints { make in
make.leading.equalTo(recordButton.snp.trailing).offset(44.0)
make.width.equalTo(64.0)
make.height.equalTo(63.0)
make.centerY.equalTo(recordButton)
}
}
private func setEvent() {
// 录制按钮
recordButton.addTarget(self, action: #selector(recordButtonClick), for: .touchUpInside)
// 重新录制按钮
reRecordButton.addTarget(self, action: #selector(reRecordButtonClick), for: .touchUpInside)
// 完成按钮
finishButton.addTarget(self, action: #selector(finishButtonClick), for: .touchUpInside)
}
/// 录制事件
@objc private func recordButtonClick() {
recordButtonClickBlock?()
}
/// 重新录制事件
@objc private func reRecordButtonClick() {
reRecordButtonClickBlock?()
}
/// 完成事件
@objc private func finishButtonClick() {
finishButtonClickBlock?()
}
/// 设置录制状态
/// - Parameter state: 状态
func setRecordingState(state: MWRecordingControlState) {
switch state {
case .normal:
recordButton.isSelected = true
recordTimeLabel.isHidden = true
reRecordButton.isHidden = true
finishButton.isHidden = true
case .recording:
recordButton.isSelected = false
recordTimeLabel.isHidden = false
reRecordButton.isHidden = true
finishButton.isHidden = true
case .finish:
recordButton.isSelected = true
recordTimeLabel.isHidden = false
reRecordButton.isHidden = false
finishButton.isHidden = false
}
}
/// 更新时间
/// - Parameter time: 时间
func updateTime(time: TimeInterval) {
recordTimeLabel.text = time.formattedMS
}
}
MWRecordingControlView 提供了以下几个核心功能:
- 录制按钮(recordButton):用于控制录制的开始和暂停。根据状态变化,按钮的样式会自动调整。
- 录制时间(recordTimeLabel):显示当前录制的时长,动态更新。
- 重新录制按钮(reRecordButton):在录制完成后,允许用户重新开始录制。
- 完成按钮(finishButton):会在点击完成时,将录制好的音频文件传递到需要的地方。
通过设置不同的录制状态(normal、recording和finish),控制视图中的各个按钮和标签的显示与隐藏,确保用户能够清晰地看到当前操作的状态。
MWRecordingButton:录制按钮
MWRecordingButton 是自定义的录制按钮,通过多层图层效果展现录制状态,并使用CALayer和CAGradientLayer创建了独特的视觉效果。其设计目标是提供一个既美观又符合录制功能需求的按钮,用户可以通过点击它来启动或停止视频录制。
以下是 MWRecordingButton 类的实现代码:
import UIKit
class MWRecordingButton: UIButton {
/// 大圆图层
private let bigCircleLayer = CALayer()
/// 小圆图层
private let smallCircleLayer = CALayer()
/// 方形图层
private let squareLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
setupLayer()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupLayer() {
// 大圆
bigCircleLayer.backgroundColor = UIColor.white.withAlphaComponent(0.6).cgColor
bigCircleLayer.cornerRadius = 36.0
layer.addSublayer(bigCircleLayer)
// 小圆
smallCircleLayer.backgroundColor = UIColor.white.withAlphaComponent(1.0).cgColor
smallCircleLayer.cornerRadius = 30
layer.addSublayer(smallCircleLayer)
// 方形
squareLayer.colors = [UIColor.wm_hex("FF3498").cgColor, UIColor.wm_hex("FF4545").cgColor]
squareLayer.cornerRadius = 6.0
layer.addSublayer(squareLayer)
}
override var isSelected: Bool {
didSet {
squareLayer.isHidden = isSelected
}
}
override func layoutSubviews() {
super.layoutSubviews()
let width = bounds.width
let height = bounds.height
// 大圆
bigCircleLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
// 小圆
let smallWidth = 60.0
smallCircleLayer.frame = CGRect(x: (width - smallWidth) / 2, y: (height - smallWidth) / 2, width: smallWidth, height: smallWidth)
// 方形
let squareWidth = 25.0
squareLayer.frame = CGRect(x: (width - squareWidth) / 2, y: (height - squareWidth) / 2, width: squareWidth, height: squareWidth)
}
}
- 大圆图层(bigCircleLayer):这个圆形图层充当按钮的背景,使用 CALayer 创建,设置了半透明白色背景,使其看起来既简洁又具有层次感。
- 小圆图层 (smallCircleLayer):这个圆形图层在大圆内部显示,颜色为纯白,模拟了录制按钮中的核心圆形。
- 方形图层(squareLayer):这个图层使用 CAGradientLayer 绘制了一个渐变色的矩形,位于小圆的中心。它的显示与按钮的选择状(isSelected)态相关,状态为selected时,方形会被隐藏。
通过这种设计,MWRecordingButton在录制和暂停状态之间切换时,能够清晰地反馈状态,用户一眼就能判断当前按钮的作用,同时通过动态的图层切换,使得界面更加生动。
MWRecordingNavigationView:导航栏
MWRecordingNavigationView 是自定义的视频录制界面顶部导航栏视图,它包含了三个主要组件:返回按钮、切换摄像头按钮和一个可定制的中间文案标签。这个视图旨在为用户提供流畅的操作体验,在录制过程中可以方便地进行控制和反馈。
以下是 MWRecordingNavigationView 类的实现代码:
import UIKit
class MWRecordingNavgationView: UIView {
/// 返回按钮
private let backButton = UIButton()
/// 切换摄像头按钮
private let switchCameraButton = UIButton()
/// 返回按钮点击事件
var backButtonClickBlock: (() -> Void)?
/// 切换摄像头按钮点击事件
var switchCameraButtonClickBlock: (() -> Void)?
/// 隐藏显示切换按钮
var isHiddenSwitchCameraButton: Bool = false {
didSet {
switchCameraButton.isHidden = isHiddenSwitchCameraButton
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
setLayout()
setEvent()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
// 返回按钮
addSubview(backButton)
backButton.setImage(UIImage(named: "navigation_back_white"), for: .normal)
// 切换摄像头按钮
addSubview(switchCameraButton)
switchCameraButton.setImage(UIImage(named: "navigation_switch_camera"), for: .normal)
}
private func setLayout() {
// 设置返回按钮的布局
backButton.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16.0)
make.centerY.equalToSuperview()
make.width.height.equalTo(32.0)
}
// 设置切换摄像头按钮的布局
switchCameraButton.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-16.0)
make.centerY.equalToSuperview()
make.width.height.equalTo(32.0)
}
}
private func setEvent() {
// 返回按钮点击事件
backButton.addTarget(self, action: #selector(backButtonClick), for: .touchUpInside)
// 切换摄像头按钮点击事件
switchCameraButton.addTarget(self, action: #selector(switchCameraButtonClick), for: .touchUpInside)
}
@objc private func backButtonClick() {
backButtonClickBlock?()
}
@objc private func switchCameraButtonClick() {
switchCameraButtonClickBlock?()
}
}
- 返回按钮(backButton):通过UIButton实现,提供一个简单的返回操作,用户可以随时退出当前的录制界面。图标为白色箭头,易于辨识。
- 切换摄像头(switchCameraButton):也是一个UIButton,用于切换前后摄像头。在某些应用场景下,用户可能需要切换摄像头,这个按钮就提供了这个功能。
结语
在本文中,我们详细探讨了 iOS 视频录制功能中的自定义 UI 实现,包括预览视图、控制视图和导航栏的设计与实现。这些自定义组件不仅提升了用户体验,还确保了操作的流畅性和可控性。通过自定义 MWRecordingPreview 视图,我们为录制过程提供了实时的视频预览;通过设计 MWRecordingControlView 和 MWRecordingButton,我们实现了清晰直观的录制控制;而 MWRecordingNavigationView 则为用户提供了便捷的导航和摄像头切换功能。
这些 UI 组件的灵活性和可定制性,使得在开发过程中可以根据需求对界面进行调整与扩展。在未来的版本中,您可以继续根据产品需求对这些视图进行优化和改进,带给用户更加丰富和优质的录制体验。
通过对这些自定义 UI 组件的解析,我们希望能够帮助开发者们更好地理解和实现 iOS 视频录制功能,并为其应用增添更多个性化的设计。感谢您的阅读,期待您的探索和创新!