Skip to content

Instantly share code, notes, and snippets.

@gngrwzrd
Last active August 18, 2024 04:14
Show Gist options
  • Save gngrwzrd/c603bfcb09210cd21dff6a4edc8f3456 to your computer and use it in GitHub Desktop.
Save gngrwzrd/c603bfcb09210cd21dff6a4edc8f3456 to your computer and use it in GitHub Desktop.
//
// VisibilityTracker.swift
// Babylist
//
// Created by Aaron Smith on 8/16/24.
// Copyright © 2024 Babylist. All rights reserved.
//
import Foundation
protocol VisibilityTrackingView : UIView {
var visibilityTrackingId:AnyHashable { get }
func performVisibilityTracking() -> Bool
}
/// This is optional and provides a way to specify the exact views
/// we know are visibilty trackable
protocol VisibilityTrackingViewSource {
var visibilityTrackingViewsToTrack:[UIView] { get }
}
class VisibilityTracker {
/// The required height visible
var amountVisible:CGFloat = 0.9
/// Optional source for views to track.
var viewSource:VisibilityTrackingViewSource?
/// Views we've tracked already
var tracked:[AnyHashable:Bool] = [:]
func scrollViewDidScroll(scrollView:UIScrollView) {
let views:[UIView] = if let viewSource {
viewSource.visibilityTrackingViewsToTrack
} else {
scrollView.subviews
}
track(views: views, scrollView: scrollView)
}
func scrollViewDidScroll(collectionView:UICollectionView) {
let views:[UIView] = if let viewSource {
viewSource.visibilityTrackingViewsToTrack
} else {
collectionView.visibleCells
}
track(views: views, scrollView: collectionView)
}
func scrollViewDidScroll(tableView:UITableView) {
let views:[UIView] = if let viewSource {
viewSource.visibilityTrackingViewsToTrack
} else {
tableView.visibleCells
}
track(views: views, scrollView: tableView)
}
private func track(views:[UIView], scrollView:UIScrollView) {
for view in views {
// if we encounter a view that is also a source, track those.
// this allows cells to be sources that provide explicit views (any subviews) to track.
if let view = view as? VisibilityTrackingViewSource {
track(views: view.visibilityTrackingViewsToTrack, scrollView: scrollView)
}
guard let visibilityView = view as? (any VisibilityTrackingView) else { continue }
let intersection = CGRectIntersection(scrollView.bounds, view.frame)
if intersection.size.height >= (view.bounds.size.height * amountVisible) {
let id = visibilityView.visibilityTrackingId
if tracked[id] == nil {
if visibilityView.performVisibilityTracking() {
tracked[id] = true
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment