深入解析iOS视频录制(二):自定义UI的实现

news2025/2/21 7:28:03

深入解析 iOS 视频录制(一):录制管理核心MWRecordingController 类的设计与实现

深入解析iOS视频录制(二):自定义UI的实现​​​​​​​

深入解析 iOS 视频录制(三):完整录制流程的实现与整合​​​​​​​

引言

在上一篇博客中,我们深入解析了 iOS 视频录制功能的核心类 MWRecordingController 的实现,涵盖了如何管理视频会话、设置输入输出、控制摄像头以及录制的启动与停止等重要功能。本篇博客将接着上文,探索自定义 UI 的实现,重点讲解如何通过 MWRecordingPreview 预览视图、MWRcordingControlView 控制视图以及精心设置的自定义按钮来提升视频录制的用户体验。

我们将逐步揭开这些自定义 UI 组件背后的设计与实现,了解如何通过灵活的视图布局与动画效果,让录制过程更加直观与流畅。希望通过本篇博客,能够帮助大家更深入地理解如何在 iOS 中定制出高质量的视频录制界面。

UI实现

在视频录制功能中,UI 设计不仅仅是视觉呈现,更是与用户交互的桥梁。一个直观、流畅且具有良好反馈的界面,能够大大提升用户的使用体验。通过自定义 UI,我们能够精细控制各个交互细节,确保用户能够快速而顺畅地完成录制操作。

本篇博客将从三个核心部分入手,详细讲解自定义 UI 的实现:

  1. 预览视图的实现:如何设计并展示视频录制的预览内容,确保录制时用户能够实时看到画面。
  2. 控制视图的实现:包括录制、暂停、重新录制以及完成按钮等,通过合理布局和状态管理,使得控制操作清晰易懂。
  3. 顶部导航栏视图的实现:导航栏中的返回按钮与切换摄像头按钮如何与视频录制功能进行无缝集成。

接下来,我们将逐一展开讲解这些关键视图的设计与实现,带大家了解如何通过代码构建一个高效且美观的视频录制界面。

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
        }
    }
}
  1. layerClass:通过重写 layerClass 方法,我们指定了该视图的图层类型为 AVCaptureVideoPreviewLayer,这是一个专门用于显示摄像头预览内容的图层类。
  2. previewLayer:该属性返回类型为A VCaptureVideoPreviewLayer 的图层,我们可以通过它来访问与摄像头相关的配置。
  3. 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 提供了以下几个核心功能:

  1. 录制按钮(recordButton):用于控制录制的开始和暂停。根据状态变化,按钮的样式会自动调整。
  2. 录制时间(recordTimeLabel):显示当前录制的时长,动态更新。
  3. 重新录制按钮(reRecordButton):在录制完成后,允许用户重新开始录制。
  4. 完成按钮(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)
    }
}
  1. 大圆图层(bigCircleLayer):这个圆形图层充当按钮的背景,使用 CALayer 创建,设置了半透明白色背景,使其看起来既简洁又具有层次感。
  2. 小圆图层 (smallCircleLayer):这个圆形图层在大圆内部显示,颜色为纯白,模拟了录制按钮中的核心圆形。
  3. 方形图层(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?()
    }
}
  1. 返回按钮(backButton):通过UIButton实现,提供一个简单的返回操作,用户可以随时退出当前的录制界面。图标为白色箭头,易于辨识。
  2. 切换摄像头(switchCameraButton):也是一个UIButton,用于切换前后摄像头。在某些应用场景下,用户可能需要切换摄像头,这个按钮就提供了这个功能。

结语

在本文中,我们详细探讨了 iOS 视频录制功能中的自定义 UI 实现,包括预览视图、控制视图和导航栏的设计与实现。这些自定义组件不仅提升了用户体验,还确保了操作的流畅性和可控性。通过自定义 MWRecordingPreview 视图,我们为录制过程提供了实时的视频预览;通过设计 MWRecordingControlView 和 MWRecordingButton,我们实现了清晰直观的录制控制;而 MWRecordingNavigationView 则为用户提供了便捷的导航和摄像头切换功能。

这些 UI 组件的灵活性和可定制性,使得在开发过程中可以根据需求对界面进行调整与扩展。在未来的版本中,您可以继续根据产品需求对这些视图进行优化和改进,带给用户更加丰富和优质的录制体验。

通过对这些自定义 UI 组件的解析,我们希望能够帮助开发者们更好地理解和实现 iOS 视频录制功能,并为其应用增添更多个性化的设计。感谢您的阅读,期待您的探索和创新!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2300803.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Deepseek 万能提问公式:高效获取精准答案

### **Deepseek 万能提问公式:高效获取精准答案** 在使用 Deepseek 或其他 AI 工具时,提问的质量直接决定了答案的精准度和实用性。以下是一个万能的提问公式回答: --- ### **1. 明确背景(Context)** - **作用**…

DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地

对于个人开发者或尝鲜者而言,本地想要部署 DeepSeek 有很多种方案,但是一旦涉及到企业级部署,则步骤将会繁琐很多。 比如我们的第一步就需要先根据实际业务场景评估出我们到底需要部署什么规格的模型,以及我们所要部署的模型&…

算法——舞蹈链算法

一,基本概念 算法简介 舞蹈链算法(Dancing Links,简称 DLX)是一种高效解决精确覆盖问题的算法,实际上是一种数据结构,可以用来实现 X算法,以解决精确覆盖问题。由高德纳(Donald E.…

WPF8-常用控件

目录 写在前面:1. 按钮控件1.1. Button 按钮1.2. RepeatButton:长按按钮1.3. RadioButton:单选按钮 2. 数据显示控件2.1. TextBlock:只读文本控件2.2. Lable:标签 显示文本控件2.3. ListBox:显示可选择项的列表2.4. DataGrid&…

代码随想录刷题day24|(字符串篇)151.反转字符串中的单词

一、题目思路 1.快慢指针移除字符串首尾以及单词中的多余空格 类似前面数组篇--移除元素代码随想录刷题day02|(数组篇)27.移除元素、26.删除有序数组中的重复项_代码随想录网站-CSDN博客 快指针fast遍历整个字符串,慢指针slow指向新字符串…

VMware按照的MacOS升级后无法联网

背景 3年前公司使用Flutter开发了一款app,现在app有微小改动需要重新发布到AppStore 问题 问题是原来的Vmware搭建的开发环境发布App失败了 提示:App需要使用xcode15IOS 17 SDK重新构建,这样的话MacOS至少需要升级到13.5 Xcode - 支持 - Ap…

DeepSeek V3和R1

DeepSeek V3 和 R1 是深度求索(DeepSeek)推出的两款大模型,基于混合专家架构(MoE),但在设计目标、训练方法和应用场景上存在显著差异。以下是两者的详细对比与补充内容: DeepSeek V3和R1 一、模…

【操作系统】深入理解Linux物理内存

物理内存的组织结构 我们平时所称的内存也叫随机访问存储器也叫 RAM 。RAM 分为两类: 一类是静态 RAM( SRAM ),这类 SRAM 用于 CPU 高速缓存 L1Cache,L2Cache,L3Cache。其特点是访问速度快,访…

记一次一波三折的众测SRC经历

视频教程和更多福利在我主页简介或专栏里 (不懂都可以来问我 专栏找我哦) 目录: 前言 波折一:RCE漏洞利用失败 波折二:SQL时间盲注 波折三:寻找管理后台 总结 前言 先谈个人SRC心得体会吧,我虽…

POI优化Excel录入

57000单词原始录入时间258S 核心代码: List<Word> wordBookList ExcelUtil.getReader(file.getInputStream()).readAll(Word.class);if (!CollectionUtil.isEmpty(wordBookList)) {for (Word word : wordBookList) {//逐条向数据库中插入单词wordMapper.insert(word);}…

HarmonyOS进程通信及原理

大家好&#xff0c;我是学徒小z&#xff0c;最近在研究鸿蒙中一些偏底层原理的内容&#xff0c;今天分析进程通信给大家&#xff0c;请用餐&#x1f60a; 文章目录 进程间通信1. 通过公共事件&#xff08;ohos.commonEventManager&#xff09;公共事件的底层原理 2. IPC Kit能…

DeepSeek核心算法解析:如何打造比肩ChatGPT的国产大模型

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《自然语言处理原理与实战》&#xff08;人工智能科学与技术丛书&#xff09;【陈敬雷编著】【清华大学出版社】 文章目录 DeepSeek大模型技术系列一DeepSeek核心算法解析&#xff1a;如何…

【算法】双指针(上)

目录 双指针 左右指针(对撞指针) 快慢指针 移动零 双指针解题 复写零 暴力解题 双指针解题(快慢指针) 快乐数 双指针解题(快慢指针) 盛最多水的容器 暴力解题(会超时) 双指针解题(左右指针) 有效三角形的个数 暴力解题 双指针解题(左右指针) 双指针 常见的双指…

深度学习模型常用激活函数集合

激活函数是深度学习模型中的关键组成部分&#xff0c;用于引入非线性特性&#xff0c;使神经网络能够学习复杂的模式和映射关系&#xff1b;神经网络本质上是一个复合函数。如果没有激活函数&#xff0c;无论网络有多少层&#xff0c;其输出都只是输入的线性组合。激活函数通过…

WebAssembly 3.0发布:浏览器端高性能计算迎来新突破!

“WebAssembly 3.0来了&#xff0c;浏览器端的高性能计算将彻底改变&#xff01;”2025年&#xff0c;WebAssembly&#xff08;Wasm&#xff09;迎来了重大更新——WebAssembly 3.0正式发布。这次更新不仅支持多线程和SIMD指令集&#xff0c;还优化了内存管理&#xff0c;让浏览…

ERP对制造业务有何价值?

ERP 的定义 在定义 ERP 之前&#xff0c;我们先从其首字母缩写说起&#xff0c;ERP 代表企业资源规划。我们可以将 ERP 定义为一种企业软件&#xff0c;它帮助组织管理日常业务。从根本上讲&#xff0c;ERP 将客户管理、人力资源、商业智能、财务管理、库存以及供应链功能整合…

哈希表(C语言版)

文章目录 哈希表原理实现(无自动扩容功能)代码运行结果 分析应用 哈希表 如何统计一段文本中&#xff0c;小写字母出现的次数? 显然&#xff0c;我们可以用数组 int table[26] 来存储每个小写字母出现的次数&#xff0c;而且这样处理&#xff0c;效率奇高。假如我们想知道字…

亚马逊企业购大客户业务拓展经理张越:跨境电商已然成为全球零售电商领域中熠熠生辉的强劲增长点

2024年12月26日-27日&#xff0c;由中国产业海外发展协会上合-海湾双链专委会指导、极新主办的「重度垂直2024极新AIGC峰会」先后在深圳、香港两地顺利开幕。本届峰会以AI的垂直应用与出海为核心主题&#xff0c;旨在深入探讨AI技术在全球范围内的融合应用与发展趋势&#xff0…

VirtualBox 中使用 桥接网卡 并设置 MAC 地址

在 VirtualBox 中使用 桥接网卡 并设置 MAC 地址&#xff0c;可以按照以下步骤操作&#xff1a; 步骤 1&#xff1a;设置桥接网卡 打开 VirtualBox&#xff0c;选择你的虚拟机&#xff0c;点击 “设置” (Settings)。进入 “网络” (Network) 选项卡。在 “适配器 1” (Adapt…

idea无法联网,离线安装插件

插件地址&#xff1a;https://plugins.jetbrains.com/ JetBrains Marketplace 如果无法进入&#xff0c;可以试试 配置hosts 3.163.125.103 plugins.jetbrains.com ip 变了&#xff0c;可以查询个最新的&#xff1a; https://tool.chinaz.com/speedtest/plugins.jetbrai…