Skip to main content
The app uses several custom view components to create the TikTok-like user interface with specialized interactions and animations.

HomeTableViewCell

Full-screen table view cell displaying a single video with interaction controls. Location: Modules/Home/HomeTableViewCell.swift:18

Key Features

  • Embedded video player with looping
  • Like animation with heart gesture
  • Pause/play tap gesture
  • Comment pop-up integration
  • Profile navigation
  • Marquee scrolling music label

Properties

playerView
VideoPlayerView
Custom video player view managing AVPlayer
profileImgView
UIImageView
User profile image with tap gesture for navigation
nameBtn
UIButton
Button displaying author username
captionLbl
UILabel
Video caption/description text
musicLbl
MarqueeLabel
Scrolling label showing background music name
likeBtn
UIButton
Like button with heart icon
commentBtn
UIButton
Opens comment pop-up view
isPlaying
Bool
Current playback state (read-only)
liked
Bool
Whether user has liked the video

Configuration

override func awakeFromNib() {
    super.awakeFromNib()
    selectionStyle = .none
    playerView = VideoPlayerView(frame: self.contentView.frame)
    musicLbl.holdScrolling = true
    musicLbl.animationDelay = 0
    
    contentView.addSubview(playerView)
    contentView.sendSubviewToBack(playerView)
    
    let pauseGesture = UITapGestureRecognizer(target: self, 
        action: #selector(handlePause))
    self.contentView.addGestureRecognizer(pauseGesture)
    
    let likeDoubleTapGesture = UITapGestureRecognizer(target: self, 
        action: #selector(handleLikeGesture(sender:)))
    likeDoubleTapGesture.numberOfTapsRequired = 2
    self.contentView.addGestureRecognizer(likeDoubleTapGesture)
    
    pauseGesture.require(toFail: likeDoubleTapGesture)
}

Playback Control

func play() {
    if !isPlaying {
        playerView.play()
        musicLbl.holdScrolling = false
        isPlaying = true
    }
}

func pause() {
    if isPlaying {
        playerView.pause()
        musicLbl.holdScrolling = true
        isPlaying = false
    }
}

func replay() {
    if !isPlaying {
        playerView.replay()
        play()
    }
}

Like Animation

Double-tap gesture creates an animated heart at tap location:
@objc func handleLikeGesture(sender: UITapGestureRecognizer) {
    let location = sender.location(in: self)
    let heartView = UIImageView(image: UIImage(systemName: "heart.fill"))
    heartView.tintColor = .red
    let width: CGFloat = 110
    heartView.contentMode = .scaleAspectFit
    heartView.frame = CGRect(x: location.x - width / 2, 
                            y: location.y - width / 2, 
                            width: width, height: width)
    heartView.transform = CGAffineTransform(rotationAngle: 
        CGFloat.random(in: -CGFloat.pi * 0.2...CGFloat.pi * 0.2))
    self.contentView.addSubview(heartView)
    
    UIView.animate(withDuration: 0.3, delay: 0, 
                  usingSpringWithDamping: 0.8, 
                  initialSpringVelocity: 3, 
                  options: [.curveEaseInOut], 
                  animations: {
        heartView.transform = heartView.transform.scaledBy(x: 0.85, y: 0.85)
    }, completion: { _ in
        UIView.animate(withDuration: 0.4, delay: 0.1, 
                      usingSpringWithDamping: 0.8, 
                      initialSpringVelocity: 3,
                      options: [.curveEaseInOut], 
                      animations: {
            heartView.transform = heartView.transform.scaledBy(x: 2.3, y: 2.3)
            heartView.alpha = 0
        }, completion: { _ in
            heartView.removeFromSuperview()
        })
    })
    likeVideo()
}

Pause Toggle Animation

@objc func handlePause() {
    if isPlaying {
        UIView.animate(withDuration: 0.075, delay: 0, 
                      options: .curveEaseIn, animations: { [weak self] in
            guard let self = self else { return }
            self.pauseImgView.alpha = 0.35
            self.pauseImgView.transform = CGAffineTransform(scaleX: 0.45, y: 0.45)
        }, completion: { [weak self] _ in
            self?.pause()
        })
    } else {
        UIView.animate(withDuration: 0.075, delay: 0, 
                      options: .curveEaseInOut, animations: { [weak self] in
            guard let self = self else { return }
            self.pauseImgView.alpha = 0
        }, completion: { [weak self] _ in
            self?.play()
            self?.pauseImgView.transform = .identity
        })
    }
}

CommentPopUpView

Modal bottom sheet displaying video comments with drag-to-dismiss gesture. Location: Modules/Home/CommentPopUpView.swift:12

Key Features

  • Bottom sheet modal presentation
  • Pan gesture for dismissal
  • Blur effect background
  • Comment table view
  • Rounded top corners

Properties

backgroundView
UIView
Dimmed background that dismisses popup when tapped
popUpView
UIView
Main container with rounded corners and blur effect
commentTableView
UITableView
Table view displaying comment cells
totalSlidingDistance
CGFloat
Tracks pan gesture distance for dismissal logic

Implementation

init() {
    super.init(frame: CGRect(x: 0, y: ScreenSize.Height, 
                            width: ScreenSize.Width, 
                            height: ScreenSize.Height))
    setupView()
}

func setupView() {
    panGesture = UIPanGestureRecognizer(target: self, 
        action: #selector(animatePopUpView(sender:)))
    
    // Background
    backgroundView = UIView(frame: self.bounds)
    backgroundView.backgroundColor = .clear
    backgroundView.isUserInteractionEnabled = true
    addSubview(backgroundView)
    let tapGesture = UITapGestureRecognizer(target: self, 
        action: #selector(handleDismiss(sender:)))
    backgroundView.addGestureRecognizer(tapGesture)
    
    // Pop Up View
    popUpView = UIView(frame: CGRect(x: 0, y: ScreenSize.Height * 0.25, 
                                    width: ScreenSize.Width, 
                                    height: ScreenSize.Height * 0.75))
    popUpView.backgroundColor = UIColor.black.withAlphaComponent(0.9)
    addSubview(popUpView)
    popUpView.addGestureRecognizer(panGesture)
}

RecordButton

Custom animated recording button with visual feedback. Location: Modules/Media/Views/RecordButton.swift:11

Key Features

  • Two-layer animation system
  • Transforms from circle to rounded square
  • Color and scale animations
  • Movable during recording

Properties

outerLayer
CALayer
Outer border ring that scales and changes color
innerLayer
CALayer
Inner fill layer that transforms to rounded square
isRecording
Bool
Current recording state
originalRadius
CGFloat
Base radius for circular shape (default: 45)
animationDuration
Double
Duration of animations (default: 0.35)

Implementation

override func awakeFromNib() {
    super.awakeFromNib()
    originalCenter = self.center
    
    outerLayer = CAShapeLayer()
    outerLayer.frame = self.bounds
    outerLayer.cornerRadius = originalRadius
    outerLayer.backgroundColor = UIColor.clear.cgColor
    outerLayer.borderColor = UIColor.Red.withAlphaComponent(0.5).cgColor
    outerLayer.borderWidth = outerLineWidth
    self.layer.addSublayer(outerLayer)
    
    innerLayer = CAShapeLayer()
    let offset = outerLineWidth + spacing
    innerLayer.frame = CGRect(x: offset, y: offset, 
                             width: (originalRadius - offset) * 2, 
                             height: (originalRadius - offset) * 2)
    innerLayer.cornerRadius = originalRadius - offset
    innerLayer.backgroundColor = UIColor.Red.cgColor
    self.layer.addSublayer(innerLayer)
}

RecordAnimation Helper

class RecordAnimation: CABasicAnimation {
    override init() {
        super.init()
        duration = 0.7
        fillMode = .forwards
        isRemovedOnCompletion = false
    }
}

VideoPlayerView

Custom video player with caching and streaming capabilities. Location: Modules/Home/VideoPlayerView.swift:13

Key Features

  • AVQueuePlayer with looping
  • Video caching system
  • Progressive streaming
  • Custom resource loading
  • Aspect ratio handling

Properties

queuePlayer
AVQueuePlayer
AVFoundation player for video playback
avPlayerLayer
AVPlayerLayer
Layer displaying video content
playerLooper
AVPlayerLooper
Handles seamless video looping
videoURL
URL?
URL of the video (local or streaming)

Configuration

func configure(url: URL?, fileExtension: String?, size: (Int, Int)) {
    // Adjust aspect ratio based on video dimensions
    avPlayerLayer.videoGravity = (size.0 < size.1) ? .resizeAspectFill : .resizeAspect
    
    guard let url = url else { return }
    self.fileExtension = fileExtension
    
    VideoCacheManager.shared.queryURLFromCache(key: url.absoluteString, 
                                              fileExtension: fileExtension) { [weak self] data in
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            if let path = data as? String {
                self.videoURL = URL(fileURLWithPath: path)
            } else {
                guard let redirectUrl = url.convertToRedirectURL(scheme: "streaming") else {
                    return
                }
                self.videoURL = redirectUrl
            }
            self.originalURL = url
            
            self.asset = AVURLAsset(url: self.videoURL!)
            self.asset!.resourceLoader.setDelegate(self, queue: .main)
            
            self.playerItem = AVPlayerItem(asset: self.asset!)
            self.addObserverToPlayerItem()
            
            if let queuePlayer = self.queuePlayer {
                queuePlayer.replaceCurrentItem(with: self.playerItem)
            } else {
                self.queuePlayer = AVQueuePlayer(playerItem: self.playerItem)
            }
            
            self.playerLooper = AVPlayerLooper(player: self.queuePlayer!, 
                                              templateItem: self.queuePlayer!.currentItem!)
            self.avPlayerLayer.player = self.queuePlayer
        }
    }
}

AccessPermissionView

Permission request UI for camera and microphone access. Location: Modules/Media/Views/AccessPermissionView.swift:14

Properties

cameraAccessBtn
UIButton
Button to request camera permission
microphoneAccessBtn
UIButton
Button to request microphone permission
exitBtn
UIButton
Close button to dismiss view

State Management

func cameraAccessPermitted() {
    cameraAccessBtn.setTitleColor(.gray, for: .normal)
    cameraAccessBtn.isEnabled = false
}

func microphoneAccessPermitted() {
    microphoneAccessBtn.setTitleColor(.gray, for: .normal)
    microphoneAccessBtn.isEnabled = false
}

ProfileHeaderView

Profile header displaying user information and stats. Location: Modules/Profile/ProfileHeaderView.swift:12

Properties

profileImgView
UIImageView
Circular profile picture with border
clearCacheBtn
UIButton
Button to clear video cache

Cache Management

@IBAction func clearCache(_ sender: Any) {
    VideoCacheManager.shared.clearCache(completion: { size in
        let message = "Remove Cache Size: " + size + "MB"
        ProfileViewModel.shared.displayMessage(message: message)
        ProfileViewModel.shared.cleardCache.onNext(true)
    })
}

Build docs developers (and LLMs) love