Skip to main content

Overview

HomeViewController is the primary view controller for the app’s video feed, displaying an infinite scroll of video posts in a full-screen, paginated table view. It uses MVVM architecture with HomeViewModel and RxSwift for reactive data binding. Location: KD Tiktok-Clone/Modules/Home/HomeViewController.swift

Key Components

UI Components

mainTableView
UITableView
Full-screen table view with paging enabled for vertical video scrolling. Each cell occupies the entire screen height and displays a single video post.
loadingAnimation
AnimationView
Lottie animation view that displays a loading indicator while fetching posts from Firebase. Positioned at center with 55pt dimensions.

Properties

viewModel
HomeViewModel
View model handling business logic, data fetching from Firebase, and audio session management.
currentIndex
Int
Dynamic property tracking the currently visible video index. Marked with @objc dynamic for KVO observation.
data
[Post]
Array of Post objects representing video feed data, populated from Firebase Firestore.
disposeBag
DisposeBag
RxSwift dispose bag for managing observable subscriptions and preventing memory leaks.

Core Methods

setupView()

Configures the main table view with full-screen layout and paging behavior.
func setupView() {
    mainTableView = UITableView()
    mainTableView.backgroundColor = .black
    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
}
The table view uses isPagingEnabled = true to snap to full-screen cells, creating the signature TikTok scrolling experience.

setupBinding()

Establishes RxSwift bindings between the view model and UI components.
func setupBinding() {
    // Observe posts from view model
    viewModel.posts
        .asObserver()
        .observeOn(MainScheduler.instance)
        .subscribe(onNext: { posts in
            self.data = posts
            self.mainTableView.reloadData()
        }).disposed(by: disposeBag)
    
    // Observe loading state
    viewModel.isLoading
        .asObserver()
        .observeOn(MainScheduler.instance)
        .subscribe(onNext: { isLoading in
            if isLoading {
                self.loadingAnimation.alpha = 1
                self.loadingAnimation.play()
            } else {
                self.loadingAnimation.alpha = 0
                self.loadingAnimation.stop()
            }
        }).disposed(by: disposeBag)
    
    // Observe errors
    viewModel.error
        .asObserver()
        .observeOn(MainScheduler.instance)
        .subscribe(onNext: { err in
            self.showAlert(err.localizedDescription)
        }).disposed(by: disposeBag)
}

Lifecycle Methods

viewWillAppear(_:)
override func
Resumes video playback for the currently visible cell when the view appears.
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    if let cell = mainTableView.visibleCells.first as? HomeTableViewCell {
        cell.play()
    }
}
viewWillDisappear(_:)
override func
Pauses video playback when navigating away to preserve resources.
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if let cell = mainTableView.visibleCells.first as? HomeTableViewCell {
        cell.pause()
    }
}

Table View Implementation

UITableViewDataSource

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.data.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! HomeTableViewCell
    cell.configure(post: data[indexPath.row])
    cell.delegate = self
    return cell
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return tableView.frame.height
}
Each cell height equals the table view’s full height, ensuring one video fills the entire screen.

UITableViewDelegate

willDisplay cell
delegate method
Called before a cell appears. Updates currentIndex and pauses the video until scrolling ends.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if let cell = cell as? HomeTableViewCell {
        oldAndNewIndices.1 = indexPath.row
        currentIndex = indexPath.row
        cell.pause()
    }
}
didEndDisplaying cell
delegate method
Pauses video when cell scrolls out of view to prevent background playback and save resources.

UIScrollViewDelegate

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    let cell = self.mainTableView.cellForRow(at: IndexPath(row: self.currentIndex, section: 0)) as? HomeTableViewCell
    cell?.replay()
}
When scrolling ends, the current video automatically replays, maintaining continuous playback.

HomeCellNavigationDelegate

Implements navigation to user profile pages from video cells.
func navigateToProfilePage(uid: String, name: String) {
    self.navigationController?.pushViewController(ProfileViewController(), animated: true)
}

HomeViewModel

Location: KD Tiktok-Clone/Modules/Home/HomeViewModel.swift Handles business logic and data management for the home feed.

Properties

isLoading
BehaviorSubject<Bool>
Observable emitting loading state changes for UI updates.
posts
PublishSubject<[Post]>
Observable stream of Post arrays fetched from Firebase.
error
PublishSubject<Error>
Observable error stream for handling fetch failures.

Methods

setAudioMode()
func
Configures AVAudioSession for video playback with .playback category and .moviePlayback mode.
func setAudioMode() {
    do {
        try! AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
        try AVAudioSession.sharedInstance().setActive(true)
    } catch (let err) {
        print("setAudioMode error:" + err.localizedDescription)
    }
}
getPosts(pageNumber:size:)
func
Fetches paginated posts from Firebase Firestore.
func getPosts(pageNumber: Int, size: Int) {
    self.isLoading.onNext(true)
    PostsRequest.getPostsByPages(pageNumber: pageNumber, size: size, success: { data in
        if let data = data as? QuerySnapshot {
            for document in data.documents {
                var post = Post(dictionary: document.data())
                post.id = document.documentID
                self.docs.append(post)
            }
            self.posts.onNext(self.docs)
            self.isLoading.onNext(false)
        }
    }, failure: { error in
        self.isLoading.onNext(false)
        self.error.onNext(error)
    })
}

Usage Example

// Initialize HomeViewController
let homeVC = HomeViewController()

// Access from tab bar controller
let tabBarController = UITabBarController()
tabBarController.viewControllers = [homeVC, ...]

// Navigate to profile from cell
extension HomeViewController: HomeCellNavigationDelegate {
    func navigateToProfilePage(uid: String, name: String) {
        let profileVC = ProfileViewController()
        navigationController?.pushViewController(profileVC, animated: true)
    }
}

Dependencies

  • UIKit: Core UI framework
  • SnapKit: Auto Layout DSL
  • AVFoundation: Audio session management
  • RxSwift: Reactive programming for data binding
  • Lottie: Animation support
  • Firebase Firestore: Backend data storage
  • HomeTableViewCell: Custom cell displaying individual video posts
  • Post: Data model representing video post entities
  • PostsRequest: Network layer for Firebase queries
  • ProfileViewController: User profile view

Build docs developers (and LLMs) love