Last active
June 17, 2022 12:55
-
-
Save johndpope/a2993cb898b908e913d2bd5c4b62c7ec to your computer and use it in GitHub Desktop.
UICollectionView + Snapkit + APDynamicHeaderTableViewController mash up // no autolayout constraint code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
import Dotzu | |
import SnapKit | |
class APDynamicHeaderTableViewController : UIViewController { | |
var largeWideSize = CGSize(width: UIScreen.main.bounds.width , height: 285 ) | |
let headerView = APDynamicHeaderView () | |
let cellLayout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() | |
var feedCV:UICollectionView! | |
fileprivate var headerViewHeight:CGFloat = 80 // this will be updated by scrolling | |
fileprivate var headerBeganCollapsed = false | |
fileprivate var collapsedHeaderViewHeight : CGFloat = UIApplication.shared.statusBarFrame.height | |
fileprivate var expandedHeaderViewHeight : CGFloat = 100 | |
fileprivate var headerExpandDelay : CGFloat = 100 | |
fileprivate var tableViewScrollOffsetBeginDraggingY : CGFloat = 0.0 | |
init(collapsedHeaderViewHeight : CGFloat, expandedHeaderViewHeight : CGFloat, headerExpandDelay :CGFloat) { | |
self.collapsedHeaderViewHeight = collapsedHeaderViewHeight | |
self.expandedHeaderViewHeight = expandedHeaderViewHeight | |
self.headerExpandDelay = headerExpandDelay | |
super.init(nibName: nil, bundle: nil) | |
} | |
init () { | |
super.init(nibName: nil, bundle: nil) | |
} | |
required init(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
override func loadView() { | |
super.loadView() | |
self.view.backgroundColor = .green | |
// Cell Layout Sizes | |
cellLayout.scrollDirection = .vertical | |
cellLayout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10) | |
cellLayout.itemSize = CGSize(width: UIScreen.main.bounds.width, height: 185 + 80) | |
// Header view | |
self.view.addSubview(headerView) | |
headerView.snp.remakeConstraints { (make) -> Void in | |
make.top.left.equalToSuperview() | |
make.width.equalToSuperview() | |
make.height.equalTo(headerViewHeight) | |
} | |
// CollectionView | |
feedCV = UICollectionView(frame: .zero, collectionViewLayout: cellLayout) | |
self.view.addSubview(feedCV) | |
self.feedCV.snp.remakeConstraints { (make) -> Void in | |
make.top.equalTo(headerView.snp.bottom) // this is pegged to the header view which is going to grow in height | |
make.left.equalToSuperview() | |
make.width.equalToSuperview() | |
make.bottom.equalToSuperview() | |
} | |
feedCV.backgroundColor = .red | |
feedCV.showsVerticalScrollIndicator = true | |
feedCV.isScrollEnabled = true | |
feedCV.bounces = true | |
feedCV.delegate = self | |
feedCV.dataSource = self | |
// YOUR COLLECTIONVIEW CELL HERE!!!!! | |
feedCV.register(VideoCollectionViewCell.self, forCellWithReuseIdentifier: VideoCollectionViewCell.ID) | |
} | |
// Animate the header view to collapsed or expanded if it is dragged only partially | |
func animateHeaderViewHeight () -> Void { | |
Logger.verbose("animateHeaderViewHeight") | |
var headerViewHeightDestinationConstant : CGFloat = 0.0 | |
if (headerViewHeight < ((expandedHeaderViewHeight - collapsedHeaderViewHeight) / 2.0 + collapsedHeaderViewHeight)) { | |
headerViewHeightDestinationConstant = collapsedHeaderViewHeight | |
} else { | |
headerViewHeightDestinationConstant = expandedHeaderViewHeight | |
} | |
if (headerViewHeight != expandedHeaderViewHeight && headerViewHeight != collapsedHeaderViewHeight) { | |
let animationDuration = 0.25 | |
UIView.animate(withDuration: animationDuration, animations: { () -> Void in | |
self.headerViewHeight = headerViewHeightDestinationConstant | |
let progress = (self.headerViewHeight - self.collapsedHeaderViewHeight) / (self.expandedHeaderViewHeight - self.collapsedHeaderViewHeight) | |
self.headerView.expandToProgress(progress) | |
self.view.layoutIfNeeded() | |
}) | |
} | |
} | |
} | |
extension APDynamicHeaderTableViewController : UICollectionViewDelegate { | |
} | |
extension APDynamicHeaderTableViewController : UIScrollViewDelegate { | |
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { | |
// Clamp the beginning point to 0 and the max content offset to prevent unintentional resizing when dragging during rubber banding | |
tableViewScrollOffsetBeginDraggingY = min(max(scrollView.contentOffset.y, 0), scrollView.contentSize.height - scrollView.frame.size.height) | |
// Keep track of whether or not the header was collapsed to determine if we can add the delay of expansion | |
headerBeganCollapsed = (headerViewHeight == collapsedHeaderViewHeight) | |
} | |
func scrollViewDidScroll(_ scrollView: UIScrollView) { | |
// Do nothing if the table view is not scrollable | |
if feedCV.contentSize.height < feedCV.bounds.height { | |
return | |
} | |
var contentOffsetY = feedCV.contentOffset.y - tableViewScrollOffsetBeginDraggingY | |
// Add a delay to expanding the header only if the user began scrolling below the allotted amount of space to actually expand the header with no delay (e.g. If it takes 30 pixels to scroll up the scrollview to expand the header then don't add the delay of the user started scrolling at 10 pixels) | |
if tableViewScrollOffsetBeginDraggingY > ((expandedHeaderViewHeight - collapsedHeaderViewHeight) + headerExpandDelay) && contentOffsetY < 0 && headerBeganCollapsed { | |
contentOffsetY = contentOffsetY + headerExpandDelay | |
} | |
// Calculate how much the header height will change so we can readjust the table view's content offset so it doesn't scroll while we change the height of the header | |
let changeInHeaderViewHeight = headerViewHeight - min(max(headerViewHeight - contentOffsetY, collapsedHeaderViewHeight), expandedHeaderViewHeight) | |
headerViewHeight = min(max(headerViewHeight - contentOffsetY, collapsedHeaderViewHeight), expandedHeaderViewHeight) | |
let progress = (headerViewHeight - collapsedHeaderViewHeight) / (expandedHeaderViewHeight - collapsedHeaderViewHeight) | |
// Logger.verbose("headerViewHeight:",headerViewHeight) | |
headerView.expandToProgress(progress) | |
headerView.snp.updateConstraints { (make) -> Void in | |
make.height.equalTo(headerViewHeight) | |
} | |
// When the header view height is changing, freeze the content in the table view | |
if headerViewHeight != collapsedHeaderViewHeight && headerViewHeight != expandedHeaderViewHeight { | |
feedCV.contentOffset = CGPoint(x: 0, y: feedCV.contentOffset.y - changeInHeaderViewHeight) | |
} | |
} | |
// Animate the header view when the user ends dragging or flicks the scroll view | |
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { | |
animateHeaderViewHeight() | |
} | |
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { | |
animateHeaderViewHeight() | |
} | |
} | |
extension APDynamicHeaderTableViewController : UICollectionViewDataSource { | |
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { | |
return 100 | |
} | |
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { | |
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: VideoCollectionViewCell.ID, for: indexPath) as! VideoCollectionViewCell | |
return cell | |
} | |
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { | |
} | |
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { | |
return largeWideSize | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class SampleViewController: APDynamicHeaderTableViewController { | |
override init() { | |
super.init( collapsedHeaderViewHeight: UIApplication.shared.statusBarFrame.height, | |
expandedHeaderViewHeight: 75, | |
headerExpandDelay: 100) | |
} | |
required init(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
import SnapKit | |
import Material | |
import Dotzu | |
class VideoCollectionViewCell:CollectionViewCell { | |
static let ID = "VideoCollectionViewCell" | |
lazy var heroImageView:UIImageView = { | |
let iv = UIImageView(frame:.zero) | |
return iv | |
}() | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
} | |
override init(frame: CGRect) { | |
super.init(frame:frame) | |
// Hero Image | |
self.addSubview(self.heroImageView) | |
self.heroImageView.contentMode = .scaleAspectFit | |
self.heroImageView.clipsToBounds = true | |
self.heroImageView.snp.remakeConstraints { (make) -> Void in | |
make.edges.equalToSuperview() | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Worked for me after I'd modified the init methods for
APDynamicHeaderTableViewController
andSampleViewController
: