scrollToItem horizontal scrolling collectionView crash - swift

I am having an issue with implementing a horizontal scrollToItem collectionView. I have a collectionView (menuBar) that on tap of a cell the page collectionView will horizontally scroll to the appropriate cell. On tap of the menuBar collectionViewCell the app crashes. Not sure where the error is. Thanks in advance!
// controller class
class CurrentUserPlaceDetailsVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var titleText: String?
let cellId = "cellId"
// UI elements
lazy var contentCollectionView: UICollectionView = {
let cv = UICollectionView()
cv.backgroundColor = UIColor.red
cv.layer.zPosition = 0
cv.translatesAutoresizingMaskIntoConstraints = false
cv.layer.masksToBounds = true
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBar()
setupCollectionView()
}
func scrollToMenuIndex(_ menuIndex: Int) {
let indexPath = IndexPath(item: menuIndex, section: 0)
contentCollectionView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition(), animated: true)
}
lazy var menuBar: MenuBar = {
let mb = MenuBar()
mb.currentUserPlaceDetailsVC = self
return mb
}()
private func setupMenuBar() {
view.addSubview(menuBar)
view.addConstraintsWithFormat("H:|[v0]|", views: menuBar)
view.addConstraintsWithFormat("V:|[v0(114)]", views: menuBar)
}
func setupCollectionView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
// layout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: self.view.frame.width, height: self.view.frame.height)
let contentCollectionView:UICollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
contentCollectionView.dataSource = self
contentCollectionView.delegate = self
// register cells
contentCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellId")
contentCollectionView.backgroundColor = .white
// contentCollectionView.scrollIndicatorInsets = UIEdgeInsets(top:114, left: 10, bottom: 10, right: 10)
self.view.addSubview(contentCollectionView)
_ = contentCollectionView.anchor(view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 114, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
view.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
contentCollectionView.isPagingEnabled = true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset.x)
menuBar.horizontalBarLeftAnchorConstraint?.constant = scrollView.contentOffset.x / 4
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath)
let colors: [UIColor] = [.blue, .red, .yellow, .green]
myCell.backgroundColor = colors[indexPath.item]
return myCell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
}
// menu bar class
class MenuBar: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.headerReferenceSize = .zero
layout.sectionInset = .zero
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .green
cv.dataSource = self
cv.delegate = self
return cv
}()
let cellId = "cellId"
var currentUserPlaceDetailsVC: CurrentUserPlaceDetailsVC?
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
addConstraintsWithFormat("H:|[v0]|", views: collectionView)
addConstraintsWithFormat("V:|[v0]|", views: collectionView)
let selectedIndexPath = IndexPath(item: 0, section: 0)
collectionView.selectItem(at: selectedIndexPath, animated: false, scrollPosition: UICollectionViewScrollPosition())
setupHorizontalBar()
}
var horizontalBarLeftAnchorConstraint: NSLayoutConstraint?
func setupHorizontalBar() {
let horizontalBarView = UIView()
horizontalBarView.backgroundColor = .white
horizontalBarView.translatesAutoresizingMaskIntoConstraints = false
addSubview(horizontalBarView)
horizontalBarLeftAnchorConstraint = horizontalBarView.leftAnchor.constraint(equalTo: self.leftAnchor)
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalBarView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
horizontalBarView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1/4).isActive = true
horizontalBarView.heightAnchor.constraint(equalToConstant: 4).isActive = true
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath.item)
let x = CGFloat(indexPath.item) * frame.width / 4
horizontalBarLeftAnchorConstraint?.constant = x
UIView.animate(withDuration: 0.75, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.layoutIfNeeded()
}, completion: nil)
currentUserPlaceDetailsVC?.scrollToMenuIndex(indexPath.item)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuCell
cell.imageView.image = UIImage(named: imageNames[indexPath.item])?.withRenderingMode(.alwaysTemplate)
cell.tintColor = UIColor.rgb(91, green: 14, blue: 13)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.width / 4, height: frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

In your comment you said:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter'. I believe there is something wrong in my func scrollToMenuIndex()... when I remove that func the app does not crash.
That happens, because you inside your View Controller your collectionView declared as lazy, that means it will be calculated on-demand.
The problem is in initializing your collectionView (reason: 'UICollectionView must be initialized with a non-nil layout parameter')
In your view controller replace UICollectionView initialization with this code:
lazy var contentCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.red
cv.layer.zPosition = 0
cv.translatesAutoresizingMaskIntoConstraints = false
cv.layer.masksToBounds = true
return cv
}()
P.S. As I wrote in my comment to your question, you have reference cycle. class MenuBar should hold weak reference to your view controller to prevent this.

Related

Cannot assign value of type '(JournalListViewController) -> () -> JournalListViewController' to type 'JournalListViewController?'

I am trying to show another ViewController from a custom tab bar but, I cannot pass a view controller to the custom class.
I've tried var jlvc = HomeController() but that would crash the app.
class JournalListViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
print(self)
navigationController?.navigationBar.isTranslucent = false
let titleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: view.frame.width-32, height: view.frame.height))
titleLabel.textColor = UIColor.white
titleLabel.text = "Journal List"
titleLabel.font = UIFont.systemFont(ofSize: 20)
navigationItem.titleView = titleLabel
collectionView?.backgroundColor = UIColor.white
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell class
collectionView?.register(JournalCell.self, forCellWithReuseIdentifier: "cellId")
// To unblock view from menubar
collectionView?.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 60, right: 0)
collectionView?.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 60, right: 0)
// Do any additional setup after loading the view.
setupMenuBar()
}
let menuBar: MenuBar = {
let mb = MenuBar()
mb.jlvc = self
return mb
}()
private func setupMenuBar(){
view.addSubview(menuBar)
view.addConstraintsWithFormat(format: "H:|[v0]|", views: menuBar)
view.addConstraintsWithFormat(format: "V:[v0(60)]|", views: menuBar)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
// MARK: UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = (view.frame.width - 16 - 16) * 9 / 16 //-16 for top, - 16 for bot, 16/9 aspect ratio so * by 9/16
return CGSize(width:view.frame.width, height:height + 16 + 41) //height is height if image + additional space we added
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func showHomeController(){
let hc = HomeController()
navigationController?.pushViewController(hc, animated: true)
}
import Foundation
import UIKit
class MenuBar: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var jlvc: JournalListViewController?
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor(red: 152/255, green: 216/255, blue: 142/255, alpha: 1)
cv.dataSource = self
cv.delegate = self
return cv
}()
let cellId = "cellId"
let imageNames = ["home","camera","journal"]
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
addConstraintsWithFormat(format: "H:|[v0]|", views: collectionView)
addConstraintsWithFormat(format: "V:|[v0]|", views: collectionView)
let selectIndexPath = IndexPath(item: 0, section: 0)
collectionView.selectItem(at: selectIndexPath, animated: false, scrollPosition: [])
self.collectionView.isScrollEnabled = false
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
// Returning Cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MenuCell
cell.imageView.image = UIImage(named: imageNames[indexPath.item])?.withRenderingMode(.alwaysTemplate)
cell.tintColor = UIColor.black
cell.jlvc = self.jlvc
return cell
}
// Size of Cell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.width / 3, height: frame.height)
}
// Spaceing in between cells
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
// Selecting cell
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! MenuCell
if cell.imageView.image == UIImage(named: "home")?.withRenderingMode(.alwaysTemplate) {
jlvc?.showHomeController()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MenuCell: BaseCell {
var jlvc: JournalListViewController?
let imageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "home")?.withRenderingMode(.alwaysTemplate)
iv.tintColor = UIColor.black
return iv
}()
override var isHighlighted: Bool {
didSet{
imageView.tintColor = isHighlighted ? UIColor.white : UIColor.black
}
}
override var isSelected: Bool {
didSet{
imageView.tintColor = isSelected ? UIColor.white : UIColor.black
}
}
override func setupViews() {
super.setupViews()
addSubview(imageView)
addConstraintsWithFormat(format: "H:[v0(28)]", views: imageView)
addConstraintsWithFormat(format: "V:[v0(28)]", views: imageView)
addConstraint(NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: imageView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0))
}
}
Cannot assign value of type '(JournalListViewController) -> () -> JournalListViewController' to type 'JournalListViewController?'
let menuBar: MenuBar = {
let mb = MenuBar()
mb.jlvc = self
return mb
}()
This doesn't do what you're thinking. self in this context is the class, not the instance. You either meant to make this a lazy var (as you did with collectionView), or to assign it explicitly.

Size of cell is not equal to collectionview size

I wrote a code that I got collectionview with 4 cells and I can swipe to see each one of them.
My collectionview backgroundcolor is red and in my cell I put an image (before that I put blue color instead of an image) and I got a problem that I see little red line form the collection view in my cell.
I must say that I had problem that I had bigger line before that and I fix that by hidden navigation view of my viewcontroller.
class CheckViewController: UIViewController, UICollectionViewDataSource,
UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .red
cv.dataSource = self
cv.delegate = self
cv.isPagingEnabled = true
return cv
}()
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.setNavigationBarHidden(true, animated: true)
view.addSubview(collectionView)
// collectionView.frame = view.frame
//use autolayout instead
collectionView.anchorToTop(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
collectionView.register(PageCell.self, forCellWithReuseIdentifier: cellId)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
// func numberOfSections(in collectionView: UICollectionView) -> Int {
// return 4
// }
//
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
// cell.backgroundColor = .white
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
}
extension UIView {
func anchorToTop(top: NSLayoutYAxisAnchor? = nil, left:
NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right:
NSLayoutXAxisAnchor? = nil) {
anchorWithConstantsToTop(top: top, left: left, bottom: bottom, right: right, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0)
}
func anchorWithConstantsToTop(top: NSLayoutYAxisAnchor? = nil, left:
NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right:
NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0,leftConstant:
CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0)
{
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: topConstant).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: bottomConstant).isActive = true
}
if let left = left {
leftAnchor.constraint(equalTo: left, constant: leftConstant).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: rightConstant).isActive = true
}
}
}
And this is the image.
There are two problems
1) You are setting the cell's size relative to the view rather than the collectionView.
Your error in setting the cell's size..
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
2) It looks like your backgroundImage for the view doesn't fit 100%. You want to use another aspect ratio to fill the view. You probably want to use .scaleToFill.
Difference between UIViewContentModeScaleAspectFit and UIViewContentModeScaleToFill?

UICollectionView Datasource methods do not get called

I am refactoring my ViewControllers and one of them contains a collectionView but now the DataSource is not getting called anymore.
My ViewController:
class CoinPageVC: UIViewController, DependencyInjectionVC, Storyboarded {
lazy var mainView: CoinPageV = {
let v = CoinPageV()
v.collectionView.delegate = self
return v
}()
var coin: Coin!
var selectedBase: String!
var viewContainer: [UIView]!
var collectionViewViewDataSource: CollectionViewCoinPageDatasource?
func injectDependencys(dependency: CoinPageDependency) {
self.coin = dependency.coin
self.selectedBase = dependency.base
self.viewContainer = dependency.views
}
override func viewDidLoad() {
super.viewDidLoad()
self.collectionViewViewDataSource = CollectionViewCoinPageDatasource(data: viewContainer)
self.mainView.collectionView.dataSource = self.collectionViewViewDataSource
self.mainView.collectionView.reloadData()
}
}
extension CoinPageVC: SetMainView {}
extension CoinPageVC: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width:CGFloat = collectionView.bounds.width
let height:CGFloat = collectionView.bounds.height
// - (tabBarHeight + menuBar.frame.height + heightNavigationBarTop)
let output = Utility.shared.CGSizeMake(width, height)
return output
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let index = Int(targetContentOffset.pointee.x / view.frame.width)
let indexPath = IndexPath(item: index, section: 0)
mainView.menuBar.collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
}
}
My Datasource class:
class CollectionViewCoinPageDatasource: NSObject, UICollectionViewDataSource {
let data: [UIView]
init(data: [UIView]){
self.data = data
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//let outputCell: UICollectionViewCell
let row = indexPath.item
let outputCell = collectionView.dequeueReusableCell(withReuseIdentifier: Identifier.coinPageCollectionViewOverviewCell.rawValue, for:indexPath) as! CollectionViewCellView
outputCell.view = data[row]
return outputCell
}
}
My collectionView setup:
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let frame = CGRect(x: 0, y: 0, width: 0, height: 0)
let cv = UICollectionView(frame: frame, collectionViewLayout: layout)
cv.register(CollectionViewCellView.self, forCellWithReuseIdentifier: Identifier.coinPageCollectionViewOverviewCell.rawValue)
if let flowLayout = cv.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
cv.backgroundColor = .green
cv.isPagingEnabled = true
//cv.backgroundColor = .blue
return cv
}()
What did I miss?
I set up the datasource and also connect it to the datasource of the collectionView, but the methods do not get called.
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let frame = CGRect(x: 0, y: 0, width: 0, height: 0)
let cv = UICollectionView(frame: frame, collectionViewLayout: layout)
cv.register(CollectionViewCellView.self, forCellWithReuseIdentifier: Identifier.coinPageCollectionViewOverviewCell.rawValue)
if let flowLayout = cv.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
cv.backgroundColor = .green
cv.isPagingEnabled = true
//cv.backgroundColor = .blue
cv.delegate = self
return cv
}()

ActivityViewController Popup not on Top of the Screen

I have a share button to share a screenshot of my screen. Although I implemented a extension to get the rootViewController, the popup of my ActivityViewController is still behind the window from which I want to share the screenshot see image attached. I already tried to load my handleDismiss() function inside the handleShare(), but without success so far. Maybe someone can get me on the right track, what is going on here.
Thank you very much in advance.
Short gif of my Statistic View
import UIKit
class GameStatistics: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let blackView = UIView()
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.white
return cv
}()
let cellId = "cellId"
let sectionHeader = "sectionHeader"
let sectionFooter = "sectionFooter"
//
//
//
func showStatistics() {
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
window.addSubview(blackView)
window.addSubview(collectionView)
// Dynamic Height of Collection View
let value: CGFloat = CGFloat(statistics.count)
let height: CGFloat = value * cellHeight + (value - 1) * cellSpacing + headerHeight + footerHeight
let y = window.frame.height - height
blackView.frame = window.frame
collectionView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: height)
blackView.alpha = 0
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.collectionView.frame = CGRect(x: 0, y: y, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}, completion: nil)
}
}
#objc func handleDismiss() {
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return statistics.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! GameStatisticCell
cell.statistic = statistics[indexPath.item]
cell.layoutIfNeeded()
//dump(statistics)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: cellHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return cellSpacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return cellSpacing
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: sectionHeader, for: indexPath)
return supplementaryView
case UICollectionElementKindSectionFooter:
let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: sectionFooter, for: indexPath)
return supplementaryView
default:
fatalError("Unexpected element kind")
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: headerHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: footerHeight)
}
override init() {
super.init()
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(GameStatisticCell.self, forCellWithReuseIdentifier: cellId)
collectionView.register(GameStatisticHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: sectionHeader)
collectionView.register(GameStatisticFooter.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: sectionFooter)
}
func handleShare() {
if let window = UIApplication.shared.keyWindow {
let size = CGSize(width: window.frame.width, height: window.frame.height)
UIGraphicsBeginImageContext(size)
window.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let activityViewController = UIActivityViewController(activityItems: [image!], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceRect = CGRect(x: 0, y: 0, width: 0, height: 0)
//UIApplication.shared.keyWindow?.rootViewController?.present(activityViewController, animated: true, completion: nil)
UIApplication.topViewController?.present(activityViewController, animated: true, completion: nil)
}
}
}
Extension
extension UIApplication {
static var topViewController: UIViewController? {
return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
}
}
extension UIViewController {
var visibleViewController: UIViewController? {
if let navigationController = self as? UINavigationController {
return navigationController.topViewController?.visibleViewController
} else if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.visibleViewController
} else if let presentedViewController = presentedViewController {
return presentedViewController.visibleViewController
} else {
return self
}
}
}

Swift: UICollectionViewCell - Dismiss UICollectionView

Trying to dismiss a slide-in UICollectionView by tapping a cell in the collection view. I Am able to dismiss by tapping outside of the collection view by using a dismiss function. Thanks
Update: Found the solution using a delegate. I have marked parts of the code with the solution steps.
Find my Codes below.
HomeController:
import UIKit
class HomeController: UIViewController, UICollectionViewDelegate , UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
let userInfoCell = "userInfoCell"
let dashboardCell = "dashboardCell"
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .cyan
setupViews()
}
func setupViews() {
view.addSubview(mainCollectionView)
mainCollectionView.dataSource = self
mainCollectionView.delegate = self
mainCollectionView.register(UserInfoCell.self, forCellWithReuseIdentifier: userInfoCell)
mainCollectionView.register(DashboardCell.self, forCellWithReuseIdentifier: dashboardCell)
}
let mainCollectionView: UICollectionView = {
let windowWidth = UIScreen.main.bounds.width
let windowHeight = UIScreen.main.bounds.height
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 16
layout.scrollDirection = .vertical
let mainCV = UICollectionView(frame: .zero, collectionViewLayout: layout)
mainCV.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width - 20, height: UIScreen.main.bounds.height - 20)
mainCV.center = CGPoint(x: windowWidth - (windowWidth / 2), y: windowHeight - (windowHeight / 2))
mainCV.backgroundColor = .red
mainCV.layer.cornerRadius = 25
return mainCV
}()
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 1 {
return 1
}
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.section == 1 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: dashboardCell, for: indexPath) as! DashboardCell
cell.plusButton.addTarget(self, action: #selector(plusButtonPressed), for: .touchUpInside)
return cell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: userInfoCell, for: indexPath) as! UserInfoCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.section == 1 {
return CGSize(width: (view.frame.width / 3) - 30 , height: 100)
}
return CGSize(width: (view.frame.width / 2) - 30 , height: 200)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if section == 1 {
return UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
}
return UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
}
#objc func plusButtonPressed() {
print("Plus button pressed")
taggObjectLauncher.showSlideInImages()
}
lazy var taggObjectLauncher: SlideInViewLauncher = {
let launcher = SlideInViewLauncher()
launcher.homeController = self
return launcher
}()
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
class UserInfoCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .blue
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class DashboardCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .green
setupDashboardViews()
}
func setupDashboardViews() {
addSubview(plusButton)
}
let plusButton: UIButton = {
let button = UIButton()
button.backgroundColor = .yellow
button.setTitle("Plus", for: .normal)
button.setTitleColor(.black, for: .normal)
button.setTitleColor(.gray, for: .highlighted)
button.titleLabel?.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
button.frame = CGRect(x: 5, y: 5, width: (UIScreen.main.bounds.width / 3) - 40, height: 90)
return button
}()
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
SlideInViewLauncher Controller:
import UIKit
class SlideInViewLauncher: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var imageCategories: [ImageCategory]?
var homeController = HomeController()
var imageCell = ImageCell()
private let cellIdentifier = "ImageCell"
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.white
return cv
}()
let blackView = UIView()
#objc func showSlideInImages() {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
if let window = UIApplication.shared.keyWindow {
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
let height = CGFloat(450)
let y = window.frame.height - height
collectionView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: height)
collectionView.register(SlideInViewCell.self, forCellWithReuseIdentifier: cellIdentifier)
window.addSubview(blackView)
window.addSubview(collectionView)
blackView.frame = window.frame
blackView.alpha = 0
imageCategories = ImageCategory.sampleImageCategories()
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 1
self.collectionView.frame = CGRect(x: 0, y: y, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
}
}
#objc func handleDismiss() {
UIView.animate(withDuration: 0.5, animations: {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
})
}
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("selected")
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = imageCategories?.count {
return count
}
return 0
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! SlideInViewCell
cell.imageCategory = imageCategories?[indexPath.row]
///////////////
// Solution: Part 5
cell.cellDelegate = self
///////////////
return cell
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width , height: 200)
}
override init() {
super.init()
collectionView.dataSource = self
collectionView.delegate = self
}
}
////////////
// Solution: Part 4
extension SlideInViewLauncher: MyCustomCellDelegate {
func didPressButton() {
UIView.animate(withDuration: 0.5, animations: {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(X: 0, y: window.frame.height, width:
self.collectionView.frame.width, height:
self.collectionView.frame.height)
}
})
}
}
/////////////
SlideInViewCell Controller:
import UIKit
////////////////
//Solution: Part 1
protocol MyCustomCellDelegate: class {
func didPressButton()
}
/////////////
class SlideInViewCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
/////////////////
// Solution: Part 2
weak var cellDelegate: MyCustomDelegate?
////////////////
var homeViewController = HomeController()
var slideInViewLauncher = SlideInViewLauncher()
var imageCategory: ImageCategory? {
didSet {
if let name = imageCategory?.name {
mainCategoryLabel.text = name
}
}
}
var image: Image?
private let cellID = "objectCellID"
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let mainCategoryLabel: UILabel = {
let label = UILabel()
label.text = ""
label.font = UIFont.systemFont(ofSize: 16)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var slideInImagesCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.white
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
let dividerLineView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 0.4, alpha: 0.4)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
func setupViews() {
backgroundColor = UIColor.clear
addSubview(slideInImagesCollectionView)
addSubview(dividerLineView)
addSubview(mainCategoryLabel)
slideInImagesCollectionView.dataSource = self
slideInImagesCollectionView.delegate = self
slideInImagesCollectionView.register(ImageCell.self, forCellWithReuseIdentifier: cellID)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-14-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": mainCategoryLabel]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-14-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": dividerLineView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": slideInImagesCollectionView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[mainCategoryLabel(30)][v0][v1(0.5)]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": slideInImagesCollectionView, "v1": dividerLineView, "mainCategoryLabel": mainCategoryLabel]))
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = imageCategory?.images?.count {
return count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! ImageCell
cell.image = imageCategory?.images?[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: frame.height - 30)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(35, 10, 10, 10)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let imageSelected = imageCategory!.images?[indexPath.item]
print(imageSelected?.name ?? "Error: No item at index path")
//////////////////
// Solution: Part 3
cellDelegate?.didPressButton()
//////////////////
}
}
class ImageCell: UICollectionViewCell {
var image: Image? {
didSet {
if let name = image?.name {
nameLabel.text = name
}
if let imageName = image?.imageName {
imageView.image = UIImage(named: imageName)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.layer.masksToBounds = true
return iv
}()
let nameLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.numberOfLines = 2
label.textAlignment = .center
return label
}()
func setupViews() {
addSubview(imageView)
addSubview(nameLabel)
imageView.frame = CGRect(x: 0, y: 0, width: frame.width, height: 100)
nameLabel.frame = CGRect(x: 0, y: frame.width + 2, width: frame.width, height: 40)
}
}
Models Controller:
import UIKit
class ImageCategory: NSObject {
var name: String?
var images: [Image]?
static func sampleImageCategories() -> [ImageCategory] {
var someImages = [Image]()
let someImageCategory = ImageCategory()
someImageCategory.name = "Some Category"
let heartImage = Image()
heartImage.name = "Heart"
heartImage.imageName = "heart"
heartImage.category = "Some Category"
someImages.append(heartImage)
someImageCategory.images = someImages
// Return the array of image categories:
return [someImageCategory]
}
}
class Image: NSObject {
var id: NSNumber?
var name: String?
var category: String?
var imageName: String?
}
Was able to solve this by adding a delegate protocol. See comments in code. Also, sorry if my post does not quite follow protocol. This is my first post.
Thanks