Skip to main content
The TikTok Clone app uses a collection of specialized view controllers to manage different sections of the application. Each controller handles specific functionality and user interactions.

TabBarController

The main navigation controller that manages all primary app sections. Location: MainApplication/TabBarController.swift:12

Properties

homeNavigationController
BaseNavigationController
Navigation controller wrapping the home feed
homeViewController
HomeViewController
Displays the main video feed
discoverViewController
DiscoverViewController
Search and discovery interface
mediaViewController
MediaViewController
Video recording and creation
inboxViewController
InboxViewController
Messages and notifications
profileViewController
ProfileViewController
User profile display

Implementation

class TabBarController: UITabBarController, UITabBarControllerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
        
        tabBar.barTintColor = .black
        tabBar.isTranslucent = false
        tabBar.unselectedItemTintColor = .gray
        tabBar.tintColor = .white
        
        // Initialize view controllers
        homeViewController = HomeViewController()
        homeNavigationController = BaseNavigationController(rootViewController: homeViewController)
        discoverViewController = DiscoverViewController()
        mediaViewController = MediaViewController()
        inboxViewController = InboxViewController()
        profileViewController = ProfileViewController()
        
        // Set tab bar icons
        homeViewController.tabBarItem.image = UIImage(systemName: "house")
        homeViewController.tabBarItem.selectedImage = UIImage(systemName: "house.fill")
        
        discoverViewController.tabBarItem.image = UIImage(systemName: "magnifyingglass")
        mediaViewController.tabBarItem.image = UIImage(named: "addMedia")
        inboxViewController.tabBarItem.image = UIImage(systemName: "text.bubble")
        profileViewController.tabBarItem.image = UIImage(systemName: "person.crop.circle")
        
        viewControllers = [homeNavigationController, discoverViewController, 
                          mediaViewController, inboxViewController, profileViewController]
    }
}

Tab Bar Delegation

The media tab presents modally instead of pushing:
func tabBarController(_ tabBarController: UITabBarController, 
                     shouldSelect viewController: UIViewController) -> Bool {
    if viewController.isKind(of: MediaViewController.self) {
        let vc = UIStoryboard(name: "MediaViews", bundle: nil)
            .instantiateViewController(identifier: "MediaVC") as! MediaViewController
        let navigationController = BaseNavigationController(rootViewController: vc)
        navigationController.modalPresentationStyle = .overFullScreen
        self.present(navigationController, animated: true, completion: nil)
        return false
    }
    return true
}

HomeViewController

Displays the main vertical scrolling video feed with full-screen videos. Location: Modules/Home/HomeViewController.swift:15

Key Features

  • Paging-enabled table view for vertical scrolling
  • Video player management per cell
  • Loading animations with Lottie
  • RxSwift reactive bindings
  • Data prefetching for smooth scrolling

Properties

mainTableView
UITableView
Full-screen table view with paging enabled for vertical video scrolling
loadingAnimation
AnimationView
Lottie animation view showing loading state
viewModel
HomeViewModel
View model managing posts data and business logic
currentIndex
Int
Currently visible video index (marked as @objc dynamic for KVO)
data
[Post]
Array of post objects displayed in the feed

Setup

func setupView() {
    mainTableView = UITableView()
    mainTableView.backgroundColor = .black
    mainTableView.translatesAutoresizingMaskIntoConstraints = false
    mainTableView.tableFooterView = UIView()
    mainTableView.isPagingEnabled = true
    mainTableView.contentInsetAdjustmentBehavior = .never
    mainTableView.showsVerticalScrollIndicator = false
    mainTableView.separatorStyle = .none
    view.addSubview(mainTableView)
    
    mainTableView.snp.makeConstraints({ make in
        make.edges.equalToSuperview()
    })
    
    mainTableView.register(UINib(nibName: "HomeTableViewCell", bundle: nil), 
                          forCellReuseIdentifier: cellId)
    mainTableView.delegate = self
    mainTableView.dataSource = self
    mainTableView.prefetchDataSource = self
}

Video Playback Control

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    if let cell = mainTableView.visibleCells.first as? HomeTableViewCell {
        cell.play()
    }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if let cell = mainTableView.visibleCells.first as? HomeTableViewCell {
        cell.pause()
    }
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    let cell = self.mainTableView.cellForRow(at: IndexPath(row: self.currentIndex, section: 0)) 
        as? HomeTableViewCell
    cell?.replay()
}

MediaViewController

Handles video recording with camera controls and post-recording editing options. Location: Modules/Media/MediaViewController.swift:11

Key Features

  • Camera session management
  • Long-press recording gesture
  • Permission handling for camera and microphone
  • Video preview and playback
  • Post-recording editing options

Properties

cameraManager
CameraManager
Manages AVFoundation camera session and recording
permissionView
AccessPermissionView
UI for requesting camera and microphone permissions
recordView
RecordButton
Custom record button with animations
playerView
MediaPlayerView
Plays back recorded video for preview
videoURL
URL?
URL of the recorded video file

Recording Flow

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)
    }
}

func setUpSession() {
    setupRecognizers()
    permissionView.removeFromSuperview()
    cameraManager.delegate = self
    previewView.layer.cornerRadius = cornerRadius
    cameraManager.addPreviewLayerToView(view: previewView)
    view.sendSubviewToBack(previewView)
}

Permission Management

@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()
        }
    })
}

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: { _ in
        UIApplication.shared.open(settingsAppURL, options: [:], completionHandler: nil)
    }))
    present(alert, animated: true, completion: nil)
}

ProfileViewController

Displays user profile with collection view layout for posts grid. Location: Modules/Profile/ProfileViewController.swift:12

Key Features

  • Custom collection view flow layout
  • Profile header with user information
  • Slide bar for content filtering
  • Parallax background image effect
  • Video posts grid

Properties

collectionView
UICollectionView
Grid display of user’s video posts
profileHeader
ProfileHeaderView
Reusable header view with profile information
profileBackgroundImgView
UIImageView
Background image with parallax scroll effect

Layout Configuration

func setupView() {
    self.view.backgroundColor = .Background
    
    let collectionViewLayout = ProfileCollectionViewFlowLayout(
        navBarHeight: getStatusBarHeight())
    collectionViewLayout.minimumLineSpacing = 1
    collectionViewLayout.minimumInteritemSpacing = 0
    
    collectionView = UICollectionView(frame: .zero, 
                                     collectionViewLayout: collectionViewLayout)
    collectionView.translatesAutoresizingMaskIntoConstraints = false
    collectionView.backgroundColor = .clear
    collectionView.contentInsetAdjustmentBehavior = .never
    collectionView.alwaysBounceVertical = true
    collectionView.showsVerticalScrollIndicator = false
    collectionView.delegate = self
    collectionView.dataSource = self
    
    view.addSubview(collectionView)
    collectionView.snp.makeConstraints({ make in
        make.edges.equalToSuperview()
    })
    
    collectionView.register(UINib(nibName: "ProfileHeaderView", bundle: nil), 
        forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, 
        withReuseIdentifier: PROFILE_HEADER_ID)
}

Scroll Effects

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let offsetY = scrollView.contentOffset.y
    if offsetY < 0 {
        stretchProfileBackgroundWhenScroll(offsetY: offsetY)
    } else {
        profileBackgroundImgView.transform = CGAffineTransform(translationX: 0, y: -offsetY)
    }
}

func stretchProfileBackgroundWhenScroll(offsetY: CGFloat) {
    let scaleRatio: CGFloat = abs(offsetY) / 500.0
    let scaledHeight: CGFloat = scaleRatio * profileBackgroundImgView.frame.height
    profileBackgroundImgView.transform = CGAffineTransform(scaleX: scaleRatio + 1.0, 
                                                          y: scaleRatio + 1.0)
        .concatenating(CGAffineTransform(translationX: 0, y: scaledHeight))
}

DiscoverViewController

Search and discovery interface for exploring content. Location: Modules/Discover/DiscoverViewController.swift:11
class DiscoverViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Implementation pending
    }
}

InboxViewController

Messages and notifications center. Location: Modules/Inbox/InboxViewController.swift:11
class InboxViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Implementation pending
    }
}

Build docs developers (and LLMs) love