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
Full-screen table view with paging enabled for vertical video scrolling. Each cell occupies the entire screen height and displays a single video post.
Lottie animation view that displays a loading indicator while fetching posts from Firebase. Positioned at center with 55pt dimensions.
Properties
View model handling business logic, data fetching from Firebase, and audio session management.
Dynamic property tracking the currently visible video index. Marked with @objc dynamic for KVO observation.
Array of Post objects representing video feed data, populated from Firebase Firestore.
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
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()
}
}
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
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()
}
}
Pauses video when cell scrolls out of view to prevent background playback and save resources.
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.
Navigation
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
Observable emitting loading state changes for UI updates.
Observable stream of Post arrays fetched from Firebase.
Observable error stream for handling fetch failures.
Methods
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:)
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