Overview
MediaViewController handles the complete video recording and posting flow, including camera setup, recording controls, post-recording editing options, and navigation to the posting screen. It manages camera/microphone permissions and provides TikTok-style recording features.
Location: KD Tiktok-Clone/Modules/Media/MediaViewController.swift
Key Components
UI Components
Container view displaying the live camera preview with rounded corners (12pt radius).
Custom recording button with long-press gesture support and progress animation during recording.
Modal view displayed when camera/microphone access is not granted, with buttons to request permissions.
Video player view shown after recording completes, allowing preview before posting.
Button to proceed to the posting screen after recording is complete.
Recording Controls
Icon for adding background sound to the recording.
Icon to flip between front and rear cameras.
Icon for adjusting recording speed (slow-motion, normal, time-lapse).
Icon for applying visual filters to the camera feed.
Icon for setting a countdown timer before recording starts.
Icon for toggling camera flash.
Icon for accessing photo library to select existing videos.
Post-Recording Options
Icon for adding audio tracks after recording.
Icon for applying visual effects to recorded video.
Icon for overlaying text on the video.
Icon for adding stickers and emoji to the video.
Properties
Manager handling AVCaptureSession, camera configuration, recording operations, and file management.
URL pointing to the recorded video file in temporary storage.
Corner radius for the preview view (12.0 points).
Core Methods
setupView()
Initializes the view and checks for camera/microphone permissions.
func setupView() {
if cameraManager.cameraAndAudioAccessPermitted {
setUpSession()
} else {
self.view.addSubview(permissionView)
permissionView.cameraAccessBtn.addTarget(self, action: #selector(askForCameraAccess), for: .touchUpInside)
permissionView.microphoneAccessBtn.addTarget(self, action: #selector(askForMicrophoneAccess), for: .touchUpInside)
permissionView.exitBtn.addTarget(self, action: #selector(dismissController), for: .touchUpInside)
}
}
If permissions are not granted, the permission view is displayed instead of the camera preview.
setUpSession()
Configures the camera session and adds the preview layer to the view.
func setUpSession() {
setupRecognizers()
permissionView.removeFromSuperview()
cameraManager.delegate = self
previewView.layer.cornerRadius = cornerRadius
cameraManager.addPreviewLayerToView(view: previewView)
view.sendSubviewToBack(previewView)
}
Recording Methods
Initiates video recording and updates UI state.func startRecording() {
hideLabelsAndImages(isHidden: true)
recordView.startRecordingAnimation()
cameraManager.startRecording()
}
Stops the active recording session.func stopRecording() {
cameraManager.stopRecording()
recordView.stopRecodingAnimation()
}
RecordingDelegate callback invoked when recording completes.func finishRecording(_ videoURL: URL?, _ err: Error?) {
recordView.isHidden = true
exitBtn.isHidden = false
nextBtn.alpha = 1
addSoundImgView.alpha = 1
addSoundLbl.alpha = 1
effectsImgView.alpha = 1
effectsLbl.alpha = 1
addTextImgView.alpha = 1
addTextLbl.alpha = 1
addStickersImgView.alpha = 1
addStickersLbl.alpha = 1
if let error = err {
self.showAlert(error.localizedDescription)
} else {
self.videoURL = videoURL
}
presentPlayerView()
}
Displays the recorded video in a player view for preview.func presentPlayerView() {
if let url = videoURL {
playerView = MediaPlayerView(frame: previewView.frame, videoURL: url)
view.addSubview(playerView!)
view.bringSubviewToFront(exitBtn)
playerView?.play()
}
}
Navigation
Saves video to library and navigates to MediaPostingViewController.@IBAction func navigateToPosting(_ sender: Any) {
let vc = UIStoryboard(name: "MediaViews", bundle: nil)
.instantiateViewController(identifier: "MediaPostingVC") as! MediaPostingViewController
guard let videoURL = self.videoURL else { return }
cameraManager.saveToLibrary(videoURL: videoURL)
vc.videoURL = videoURL
self.navigationController?.pushViewController(vc, animated: true)
}
Permission Handling
askForCameraAccess()
@objc func askForCameraAccess() {
cameraManager.askForCameraAccess({ [weak self] access in
guard let self = self else { return }
if access {
self.permissionView.cameraAccessPermitted()
if (self.cameraManager.cameraAndAudioAccessPermitted) {
self.setUpSession()
}
} else {
self.alertCameraAccessNeeded()
}
})
}
@objc func askForMicrophoneAccess() {
cameraManager.askForMicrophoneAccess({ [weak self] access in
guard let self = self else { return }
if access {
self.permissionView.microphoneAccessPermitted()
if (self.cameraManager.cameraAndAudioAccessPermitted) {
self.setUpSession()
}
} else {
self.alertCameraAccessNeeded()
}
})
}
alertCameraAccessNeeded()
func alertCameraAccessNeeded() {
let settingsAppURL = URL(string: UIApplication.openSettingsURLString)!
let alert = UIAlertController(
title: "Need Camera Access",
message: "Camera access is required to make full use of this function.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alert.addAction(UIAlertAction(title: "Settings", style: .default, handler: { (alert) -> Void in
UIApplication.shared.open(settingsAppURL, options: [:], completionHandler: nil)
}))
present(alert, animated: true, completion: nil)
}
The permission flow guides users to Settings if they previously denied access.
Gesture Recognizers
setupRecognizers()
Configures tap and long-press gestures for all interactive elements.
func setupRecognizers() {
// Recording button with long press
let recordLongPressGesture = UILongPressGestureRecognizer(
target: self,
action: #selector(recordBtnPressed(sender:))
)
recordLongPressGesture.minimumPressDuration = 0
recordView.addGestureRecognizer(recordLongPressGesture)
// Sound selection
let soundTapGesture = UITapGestureRecognizer(
target: self,
action: #selector(setBackgroundSound)
)
soundLbl.addGestureRecognizer(soundTapGesture)
// Camera flip
let flipTapGesture = UITapGestureRecognizer(
target: self,
action: #selector(flipCamera)
)
flipImgView.addGestureRecognizer(flipTapGesture)
// Additional gestures for speed, filters, timer, flash, album, etc.
}
recordBtnPressed(sender:)
Handles the recording button’s long-press gesture states.
@objc fileprivate func recordBtnPressed(sender: UILongPressGestureRecognizer) {
let location = sender.location(in: self.view)
switch sender.state {
case .began:
startRecording()
case .changed:
recordView.locationChanged(location: location)
case .cancelled, .ended:
stopRecording()
default:
break
}
}
The recording starts immediately on press and stops when released, mimicking TikTok’s UX.
CameraManager
Location: KD Tiktok-Clone/Modules/Media/CameraManager.swift
Manages AVFoundation camera and recording operations.
Properties
Core capture session managing camera inputs and outputs.
Active camera device (front or rear).
cameraAndAudioAccessPermitted
Combined permission state for both camera and microphone access.
Output for recording video files with audio.
previewLayer
AVCaptureVideoPreviewLayer
Layer displaying live camera feed.
Key Methods
addPreviewLayerToView(view:)
Adds camera preview layer to the specified view.func addPreviewLayerToView(view: UIView) {
if cameraAndAudioAccessPermitted {
setupCamera {
self.embeddingView = view
DispatchQueue.main.async {
guard let previewLayer = self.previewLayer else { return }
previewLayer.frame = view.layer.bounds
view.clipsToBounds = true
view.layer.addSublayer(previewLayer)
}
}
}
}
Saves recorded video to the device’s photo library.func saveToLibrary(videoURL: URL) {
if PHPhotoLibrary.authorizationStatus() != .authorized {
PHPhotoLibrary.requestAuthorization({ status in
if status == .authorized {
self.photoLibrary?.performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
}, completionHandler: { (saved, error) in
// Handle save result
})
}
})
}
}
Cleans up temporary video files from previous recording sessions.
Lifecycle Methods
Clears temporary files and initializes the view.override func viewDidLoad() {
super.viewDidLoad()
cameraManager.removeAllTempFiles()
setupView()
}
Pauses video player when navigating away.override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
playerView?.pause()
}
Usage Example
// Present MediaViewController modally
let mediaVC = UIStoryboard(name: "MediaViews", bundle: nil)
.instantiateViewController(identifier: "MediaVC") as! MediaViewController
let navController = UINavigationController(rootViewController: mediaVC)
navController.modalPresentationStyle = .fullScreen
present(navController, animated: true)
// Handle recording completion
extension MediaViewController: RecordingDelegate {
func finishRecording(_ videoURL: URL?, _ err: Error?) {
if let url = videoURL {
// Process recorded video
self.videoURL = url
presentPlayerView()
}
}
}
Dependencies
- UIKit: Core UI framework
- AVFoundation: Camera capture and video recording
- Photos: Photo library access and saving
MediaPostingViewController: Next screen for adding captions and posting
RecordButton: Custom recording button with animation
AccessPermissionView: Permission request UI
MediaPlayerView: Video preview player
CameraManager: AVFoundation wrapper