CABasicAnimation stops when UICollectionView is reloaded - uicollectionview

I need help with an issue in Collection Views. In my collectionView I am using UILongPressGestureRecognizer to start CABasicAnimation in the items (images), but when I remove an item and reload the collectionView the CABasicAnimation is interrupeted.
How can I prevent the animation stop?
I need the animation continues until the user decide to stop.
Here is some information about my collectionView:
class SentMemesCollectionVC: UICollectionViewController {
//MARK: - PROPERTIES
var memes: [Meme]! {
return Meme.accessMemes().memes
}
//MARK: - LIFE CYCLE
override func viewDidLoad() {
super.viewDidLoad()
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longTap(_:)))
self.collectionView.addGestureRecognizer(longPressGesture)
}
#objc func longTap(_ gesture: UILongPressGestureRecognizer) {
switch gesture.state {
case .began:
print("LongPress begin")
guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else {return}
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
setEditing(true, animated: true)
default:
collectionView.cancelInteractiveMovement()
}
}
// MARK: - COLLECTIONVIEW DATA SOURCE
//Defini o número de itens em cada seção
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return memes.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as! GridMemeCell
let meme = memes[indexPath.row]
cell.prepareGridCell(with: meme)
cell.delegate = self
return cell
}
// MARK: - COLLECTIONVIEW DELEGATE
/*
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let memeDetail = storyboard?.instantiateViewController(withIdentifier: "DetailVC") as! MemeDetailVC
let meme = memes[indexPath.row]
memeDetail.memeSelected = meme
Feedback.share.hapticFeedback()
navigationController?.pushViewController(memeDetail, animated: true)
}
*/
// MARK: - DELETE ITEMS IN COLLECTVIEW
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
Feedback.share.hapticFeedback()
if let indexPaths = collectionView?.indexPathsForVisibleItems {
for indexPath in indexPaths {
if let cell = collectionView?.cellForItem(at: indexPath) as? GridMemeCell {
if editing {
cell.isEditing = editing
cell.startAnimate()
doneBarButtonItem()
} else {
cell.isEditing = editing
cell.stopAnimate()
addBarButtonItem()
}
}
}
}
}
}
Here is my CollectionViewCell
class GridMemeCell: UICollectionViewCell {
//MARK: - OUTLETS
#IBOutlet weak var ivImage: UIImageView!
#IBOutlet weak var deleteMeme: UIVisualEffectView!
//MARK: - PROPERTIES
weak var delegate: GridMemeCellDelegate?
var isAnimate: Bool! = true
//MARK: - METHODS AND COMPUTED PROPERTIES
func prepareGridCell(with meme: Meme) {
ivImage.image = meme.memeImage
deleteMeme.layer.cornerRadius = deleteMeme.bounds.width / 2.0
deleteMeme.layer.masksToBounds = true
deleteMeme.isHidden = !isEditing
deleteMeme.contentView.backgroundColor = Theme.current.subViewColor
}
func startAnimate() {
let shakeAnimation = CABasicAnimation(keyPath: "transform.rotation")
shakeAnimation.duration = 0.05
shakeAnimation.repeatCount = 4
shakeAnimation.autoreverses = true
shakeAnimation.duration = 0.2
shakeAnimation.repeatCount = 99999
let startAngle: Float = (-2) * 3.14159 / 180
let stopAngle = -startAngle
shakeAnimation.fromValue = NSNumber(value: startAngle)
shakeAnimation.toValue = NSNumber(value: 3 * stopAngle)
shakeAnimation.autoreverses = true
shakeAnimation.timeOffset = 290 * drand48()
let layer: CALayer = self.layer
layer.add(shakeAnimation, forKey: "animate")
self.deleteMeme.isHidden = false
isAnimate = true
}
func stopAnimate() {
let layer: CALayer = self.layer
layer.removeAnimation(forKey: "animate")
self.deleteMeme.isHidden = true
isAnimate = false
}
var isEditing: Bool = false {
didSet {
deleteMeme.isHidden = !isEditing
}
}
#IBAction func btDeleteMeme(_ sender: Any) {
Feedback.share.hapticFeedback()
delegate?.deleteCell(cell: self)
}
}
And here is my Protocol Delegate Cell:
protocol GridMemeCellDelegate: class {
func deleteCell(cell: GridMemeCell)
}
And here is my extension CollectionView - Delegate.
extension SentMemesCollectionVC: GridMemeCellDelegate {
func deleteCell(cell: GridMemeCell) {
if let indexPath = collectionView.indexPath(for: cell) {
//Apaga o Meme do Array
Meme.accessMemes().memes.remove(at: indexPath.item)
collectionView.reloadData()
}
}
}

Related

Using `sectionHeadersPinToVisibleBounds` while a `UIRefreshControl` is refreshing causes incorrect position of sticky headers

When using a UICollectionView with a UICollectionViewFlowLayout that has sectionHeadersPinToVisibleBounds set to true the sticky headers will be positioned incorrectly when a UIRefreshControl added to the collection view is refreshing.
Example view controller:
import UIKit
public final class StickyHeaderTestViewController: UICollectionViewController {
init() {
let layout = UICollectionViewFlowLayout()
layout.sectionHeadersPinToVisibleBounds = true
super.init(collectionViewLayout: layout)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = .white
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "UICollectionViewCell")
collectionView.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UICollectionReusableView")
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshControlValueChanged(_:)), for: .valueChanged)
collectionView.refreshControl = refreshControl
}
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = CGSize(width: view.bounds.width, height: 40)
layout.headerReferenceSize = CGSize(width: view.bounds.width, height: 40)
}
override public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UICollectionViewCell", for: indexPath)
switch indexPath.section {
case 0:
cell.backgroundColor = .blue
case 1:
cell.backgroundColor = .yellow
case 2:
cell.backgroundColor = .blue
default:
break
}
return cell
}
override public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "UICollectionReusableView", for: indexPath)
switch indexPath.section {
case 0:
view.backgroundColor = .brown
case 1:
view.backgroundColor = .green
case 2:
view.backgroundColor = .brown
default:
break
}
return view
}
override public func numberOfSections(in collectionView: UICollectionView) -> Int {
3
}
override public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
10
}
#objc private func refreshControlValueChanged(_ sender: UIRefreshControl) {
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
sender.endRefreshing()
}
}
}
Is there a workaround for this issue?

Collection view cell not appear after resize the CollectionView height

I am facing the UICollectionView cell size issue. I am using two collection_view and one table_view 1:- Main CollectionView ,which is use for swipe and only display one cell at one time 2:- Inside Main Collection_View, i use table_view and now further inside table_view I am using Collection view which display 6 cell at one time.
Now the whole process is I am using a layout which is swipable and display 6 item at one page and so on.
The problem is when I set main cell height 200 then inside collectionview cell not appearing as expected.
Outer CollectionView Class
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var collectionMain: UICollectionView!
#IBOutlet weak var tableView: UITableView!
let itemsPerRow: CGFloat = 1
let sectionInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension ViewController:UICollectionViewDataSource,UICollectionViewDelegate {
//1
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
//2
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return 3
}
//3
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:cellCollectionCollectionViewCell! = collectionView.dequeueReusableCell(withReuseIdentifier: "cellSwipe",
for: indexPath) as! cellCollectionCollectionViewCell
switch indexPath.item {
case 0:
cell.backgroundColor = UIColor.black
cell.setupCollectionTableCell(indexPath)
break
case 1: cell.backgroundColor = UIColor.red
cell.setupCollectionTableCell(indexPath)
break
default:
cell.backgroundColor = UIColor.yellow
cell.setupCollectionTableCell(indexPath)
break
}
// Configure the cell
return cell
}
}
extension ViewController : UICollectionViewDelegateFlowLayout {
//1
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
//2
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = self.collectionMain.frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: widthPerItem)
}
//3
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return sectionInsets
}
// 4
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return sectionInsets.left
}
}
Model Constant class
import UIKit
class Constant {
static let totalItem: CGFloat = 9
static let column: CGFloat = 3
static let minLineSpacing: CGFloat = 1.0
static let minItemSpacing: CGFloat = 1.0
static let offset: CGFloat = 1.0 // TODO: for each side, define its offset
static func getItemWidth(boundWidth: CGFloat) -> CGFloat {
let totalWidth = boundWidth - (offset + offset) - ((column - 1) * minItemSpacing)
return totalWidth / column
}
}
Table Cell Class
import UIKit
class CollectionCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.sectionInset = UIEdgeInsetsMake(
Constant.offset, // top
Constant.offset, // left
Constant.offset, // bottom
Constant.offset // right
)
layout.minimumInteritemSpacing = Constant.minItemSpacing
layout.minimumLineSpacing = Constant.minLineSpacing
}
collectionView.isScrollEnabled = false
collectionView.dataSource = self
collectionView.delegate = self
}
}
extension CollectionCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Int(Constant.totalItem)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BoxCell", for: indexPath)
return cell
}
}
extension CollectionCell: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let itemWidth = Constant.getItemWidth(boundWidth: collectionView.bounds.size.width)
return CGSize(width: itemWidth, height: 50)
}
}
Main CollectionCell class
import UIKit
class cellCollectionCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var tblView: UITableView!
var indexPath:IndexPath?
func setupCollectionTableCell(_ row:IndexPath){
self.indexPath = row
self.tblView.delegate = self
self.tblView.dataSource = self
// self.tblView.estimatedRowHeight = 300
self.tblView.reloadData()
}
}
extension cellCollectionCollectionViewCell:UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch indexPath.row {
case 0:
return 50
case 1:
let itemHeight = Constant.getItemWidth(boundWidth: tableView.bounds.size.width)
let totalRow = ceil(Constant.totalItem / Constant.column)
let totalTopBottomOffset = Constant.offset + Constant.offset
let totalSpacing = CGFloat(totalRow - 1) * Constant.minLineSpacing
let totalHeight = ((itemHeight * CGFloat(totalRow)) + totalTopBottomOffset + totalSpacing)
print("total height \(totalHeight)")
return totalHeight
default:
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// switch indexPath.row {
// case 0:
// let cell = tableView.dequeueReusableCell(withIdentifier: "HeaderCell", for: indexPath)
// return cell
// default:
// let cell = tableView.dequeueReusableCell(withIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
// return cell
// }
let cell = tableView.dequeueReusableCell(withIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
return cell
}
When I set height of Main Collection view 200
Bellow are the link of images
When I set height 300, working fine

UICollectionView wrong image displayed

I've found a couple of posts about issues with UICollectionView and iwrong images but all of them have one common - people try to download image in cellForItemAt method.
I already have proper image in my app bundle and simply use UIImage(named:) method to set it to UICollectionViewCell. However I still get wrong images. Could anyone help me?
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return liveChannels.count()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ChannelButtonViewCell", for: indexPath) as! ChannelButtonViewCell
let current = liveChannels.channels[indexPath.row]
let currentProgram = current.currentProgram()
let channelKey = current.channel?.channelKey
let programDetail = currentProgram?.name
if let currentProgress = currentProgram?.progress() {
cell.progressView.setProgress(currentProgress, animated: true)
}
print("\(channelKey) - \(current.channel!.logoName)")
cell.imageName = current.channel!.logoName
cell.channelTitleLabel.text = channelKey
cell.channelDetailLabel.text = programDetail
cell.channelButton.addTarget(self, action: #selector(ChannelsViewController.pressed(_:)), for: .primaryActionTriggered)
return cell
}
I'm printing channelKey and logoName to console to be sure that the proper image is stored inside of liveChannels.channels structure. (THey are correct indeed).
This is my cell:
class ChannelButtonViewCell: UICollectionViewCell {
#IBOutlet weak var progressView: UIProgressView!
#IBOutlet weak var channelButton: ChannelButton!
#IBOutlet weak var channelDetailLabel: UILabel!
#IBOutlet weak var channelTitleLabel: UILabel!
#IBOutlet weak var channelImageView: UIImageView!
var imageName: String?
override func prepareForReuse() {
channelImageView.image = nil
super.prepareForReuse()
}
override func draw(_ rect: CGRect) {
if (self.isSelected) {
if let imageName = imageName {
channelImageView.image = UIImage(named: imageName)
}
} else {
if let imageName = imageName {
channelImageView.image = UIImage(named: imageName.appending("_grey"))
}
}
}
}
1) You should override func draw(_ rect: CGRect) in very rare cases and your case is definitely not the one.
2) To ensure you load and change image exactly when you assign cell's var imageName: String? you could use Swift's didSet
3) To ensure your image updates when selection state changes you could use didSet as well.
you could end up removing func draw at all and adding didSet observers like this:
class ChannelButtonViewCell: UICollectionViewCell {
#IBOutlet weak var progressView: UIProgressView!
#IBOutlet weak var channelButton: ChannelButton!
#IBOutlet weak var channelDetailLabel: UILabel!
#IBOutlet weak var channelTitleLabel: UILabel!
#IBOutlet weak var channelImageView: UIImageView!
var imageName: String? {
didSet {
updateImage()
}
}
override var isSelected: Bool {
didSet {
updateImage()
}
}
override func prepareForReuse() {
channelImageView.image = nil
super.prepareForReuse()
}
func updateImage() {
guard let imageName = self.imageName else { return }
if (self.isSelected) {
channelImageView.image = UIImage(named: imageName)
} else {
channelImageView.image = UIImage(named: imageName.appending("_grey"))
}
}
}
If this solution doesn't solve your problem try set breakpoint inside of func draw() and check when it is called and what value stored in var imageName

CollectionView does not work when embedded in Navigation Controller

I have a simple collection view test (based on an online tutorial) which works fine stand alone. But when I embed it in a navigation controller it stops working. I built the screen in code by (1) creating a headerView (64 pixel high) and added it to the view at the top. (2) I built a collection view and added it to the headerView.
Here is the code:
import UIKit
class ViewController: UIViewController,
UICollectionViewDelegate, UICollectionViewDataSource,
UINavigationControllerDelegate
{
var collectionView : UICollectionView!
var topView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
var frame = CGRect(x:0,y:128, width:view.frame.width, height:64)
topView = UIView(frame:frame)
self.view.addSubview(topView)
// CollectionView
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
layout.itemSize = CGSize(width: 50, height: 50)
frame = CGRect(x: 0, y: 0, width: Int(self.topView.frame.width), height: Int(self.topView.frame.height))
collectionView = UICollectionView (frame: frame, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "collectionCell")
collectionView.backgroundColor = UIColor.green
self.topView.addSubview(collectionView)
}
//MARK: - CollectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 14
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath as IndexPath)
for v in cell.subviews {
v.removeFromSuperview()
}
cell.backgroundColor = UIColor.orange
let label = UILabel(frame: CGRect(x:0 , y:0 , width:50 , height:50))
label.text = "\(indexPath.item)"
label.textAlignment = .center
label.textColor = UIColor.white
cell.addSubview(label)
return cell
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I couldn't make Kaushal's suggestion work as stated. It did give me a clue that my positioning of the view in which I had embedded the collection view was being misplaced in viewDidLoad for reasons that I have not understood. However by putting the collection view configuration in viewDidAppear (rather than viewDidLoad) it worked well. I offset the y position by 64 to clear the navbar, and I reduced the row height to 64. I also, put code to execute the code only once so that navigating from pages does not add multiple views of top of each other. BTW, my original objective was to have horizontally scrolling cells. In my program I have tableview with corresponding sections and the idea is to use the row with horizontally scrolling cells to move to a corresponding section.
The code is shown below:
//
// CustomViewController.swift
// DSM Tracker
//
// Created by Syed Tariq on 1/7/17.
// Copyright © 2017 com.syedtariq. All rights reserved.
//
import UIKit
class ViewController: UIViewController,
UICollectionViewDelegate,
UICollectionViewDataSource,
UINavigationControllerDelegate
{
var executeOnce = true
var cellDimensions = [String:Int]()
var cellHeight = 50
var cellWidth = 120
var collectionContainerView: UICollectionView!
var navBar: UINavigationBar = UINavigationBar()
// view constants
var viewY = CGFloat()
var viewX = CGFloat()
var viewWidth = CGFloat()
var viewHeight = CGFloat()
// gaps from view edge
let leftGap = CGFloat(20)
let rightGap = CGFloat(20)
// navbar constants
let navBarHeight = CGFloat(64)
var headerLabels = ["Cell 01","Cell 02","Cell 03","Cell 04","Cell 05","Cell 06","Cell 07","Cell 08","Cell 09","Cell 10","Cell 11","Cell 12","Cell 13","Cell 14","Cell 15","Cell 16"]
override func viewDidLoad() {
super.viewDidLoad()
navBar.backgroundColor = UIColor.green
executeOnce = true
viewY = view.frame.origin.y
viewX = view.frame.origin.x
viewWidth = view.frame.width
viewHeight = view.frame.height
}
func configureCollectionView () {
if executeOnce {
executeOnce = false
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10)
layout.itemSize = CGSize(width: cellWidth, height: cellHeight)
let colWidth = viewWidth - leftGap - rightGap
let colX = viewX + leftGap
let colY = viewY + navBarHeight
let colHeight = CGFloat(64)
let frame = CGRect(x:colX, y:colY, width: colWidth, height: colHeight)
//let frame = CGRect.zero
collectionContainerView = UICollectionView (frame: frame, collectionViewLayout: layout)
collectionContainerView.dataSource = self
collectionContainerView.delegate = self
collectionContainerView.autoresizingMask = [.flexibleLeftMargin,.flexibleLeftMargin,.flexibleBottomMargin,.flexibleRightMargin, .flexibleHeight, .flexibleWidth]
collectionContainerView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "collectionCell")
collectionContainerView.backgroundColor = UIColor.blue
collectionContainerView.allowsSelection = true
collectionContainerView.isScrollEnabled = true
collectionContainerView.setNeedsDisplay()
print("collectionContainerView.frame \(collectionContainerView.frame)")
view.addSubview(collectionContainerView)
}
}
override func viewDidAppear(_ animated: Bool) {
configureCollectionView()
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("headerLabels.count \(headerLabels.count)")
return headerLabels.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath as IndexPath)
for v in cell.subviews {
v.removeFromSuperview()
}
let cellTitle = headerLabels[indexPath.row]
let cellTitleLines = cellTitle.components(separatedBy: " ")
let nLabels = cellTitleLines.count
cell.layer.borderWidth = 1
cell.layer.cornerRadius = 8
let labelHeight = cellHeight / cellTitleLines.count
for i in (0 ..< nLabels) {
let frame = CGRect(x: 0, y: labelHeight * i, width: cellWidth, height: labelHeight)
let label1 = UILabel(frame: frame)
cell.backgroundColor = UIColor.lightGray
label1.numberOfLines = 1
label1.text = headerLabels[indexPath.row]
label1.textAlignment = .center
label1.textColor = UIColor.black
label1.clipsToBounds = true
label1.adjustsFontSizeToFitWidth = true
label1.text = cellTitleLines[i]
cell.addSubview(label1)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
collectionContainerView.scrollToItem(at:IndexPath(item: indexPath.item, section: 0), at: .centeredHorizontally, animated: false)
return true
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionContainerView.cellForItem(at: indexPath)
print("cell = \(cell)")
collectionContainerView.scrollToItem(at:IndexPath(item: indexPath.item, section: 0), at: .centeredHorizontally, animated: false)
}
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
}
}

hidesBarsOnSwipe never shows navbar again when scrolling up

So I want to hide the navbar when scrolling down and bring it back when scrolling up. Hiding it works perfectly with
self.navigationController?.hidesBarsOnSwipe = true
But I expect it to be shown again when scrolling up. I made a test project where the view controller just has a single UICollectionView that covers the whole screen. Then showing the navbar is shown again as expected until I add this line to the viewDidLoad (adding cells to the collection view):
self.collectionView.delegate = self
And this is what the whole view controller looks like
class ViewController: UIViewController,UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "Test")
self.navigationController?.hidesBarsOnSwipe = true
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCellWithReuseIdentifier("Test", forIndexPath: indexPath) as UICollectionViewCell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(300, 300)
}
}
So why does showing the navbar stop working when I add cells to my collection view?
I had the same problem but with a web view.
The problem was that the top constraint of the web view was "Top Layout Guide.Top" , after changing the top constraint to "Superview.Top" the problem was solved.
To expand on Oleg's answer...
If you are using Interface Builder to set a constraint to a view controller's primary view, Xcode defaults to showing options to set the vertical constraint against the top layout guide. However, if you press 'Option', you will see an alternate set of constraints. The constraint for 'Top Space to Container' is what you're looking for.
I had same issue. When I added the code for hiding status bar along with navigation bar, it worked.
- (BOOL)prefersStatusBarHidden {
return self.navigationController.isNavigationBarHidden;
}
I tried setting hidesBarsOnSwipe property to true in my ViewController class in ViewDidLoad function as given below, but it didn't work in handling hiding the navigation bar on swipe-up and unhiding the navigation bar on swipe-down.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.hidesBarsOnSwipe = true
}
}
Setting hidesBarsOnSwipe to true will have effect only if we are using the UITableViewController or UICollectionViewController as main screens, hidesBarsOnSwipe will not work if we have added a UITableView to the UIViewController for displaying the list of data.
Solution
class TestTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.hidesBarsOnSwipe = true
}
}
Hope this answer might help...!
I filed a bug report with Apple and ended up using AMScrollingNavbar instead which works really well and is easy to setup.
As per previous comments - this seems like a bug as of ios 10.3
as you are using a uicollectionview - I draw your attention to some code I re-wrote from APDynamicHeaderTableViewController
https://github.com/aaronpang/APDynamicHeaderTableViewController/issues/4
It's using snapkit https://github.com/SnapKit/SnapKit
(Apologies to all the IB + NSLayout Constraint lovers.)
class APDynamicHeaderTableViewController : UIViewController {
var largeWideSize = CGSize(width: UIScreen.main.bounds.width , height: 285 )
let headerView = APDynamicHeaderView () // Change your header view here
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
}
}
To make hidesBarsOnSwipe working properly, your view controller's view must contain only UITableView instance and nothing else.