Cannot get indexPath and selection on Collection View - swift

I'm facing a problem with my collection view. I'm trying to pick the indexPath of an item to show more info on it after the tap, but I can't get it and figure out why.
I tried to allows collection View selection, but nothing happened.
I also tried to add a tap gesture on the cell, but don't know how to get the indexPath from there.
I add the same code on another VC, where the collection view is on the entire screen, and it works ( here, the collection View is half the screen ).
Here is the (updated) code for the collection View :
class SearchRunnerVC: UIViewController {
//MARK: - Objects
var lastNameTextField = RSTextField(placeholder: "Nom du coureur", returnKeyType: .next,
keyboardType: .default)
var firstNameTextField = RSTextField(placeholder: "Prénom", returnKeyType: .next,
keyboardType: .default)
var numberTextField = RSTextField(placeholder: "Numéro de dossard", returnKeyType: .next,
keyboardType: .numberPad)
var raceTextField = RSTextField(placeholder: "Course", returnKeyType: .done,
keyboardType: .default)
var searchButton = RSButton(backgroundColor: .systemPink, title: "Rechercher")
var collectionView : UICollectionView! = nil
var emptyLabel = UILabel()
let hud = JGProgressHUD(style: .dark)
lazy var textFields = [lastNameTextField, firstNameTextField, numberTextField, raceTextField]
//MARK: - Properties
let padding = CGFloat(20)
let runnersCollection = Firestore.firestore().collection("Runner")
var runners = [Runner]()
//MARK: - Lifecycle Methods
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.prefersLargeTitles = true
title = "Rechercher"
}
}
extension SearchRunnerVC {
fileprivate func setupUI() {
configureSuperView()
configureTextFields()
configureCollectionView()
configureButton()
configureConstraints()
}
fileprivate func configureSuperView() {
view.backgroundColor = .systemBackground
let viewTap = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard(_:)))
view.addGestureRecognizer(viewTap)
}
fileprivate func configureConstraints() {
for textField in textFields {
view.addSubview(textField)
textField.leftToSuperview(view.leftAnchor, offset: padding)
textField.rightToSuperview(view.rightAnchor, offset: -padding)
textField.height(50)
textField.delegate = self
}
firstNameTextField.topToSuperview(view.safeAreaLayoutGuide.topAnchor, offset: padding, usingSafeArea: true)
lastNameTextField.topToBottom(of: firstNameTextField, offset: padding)
numberTextField.topToBottom(of: lastNameTextField, offset: padding)
raceTextField.topToBottom(of: numberTextField, offset: padding)
searchButton.topToBottom(of: raceTextField, offset: padding)
searchButton.leftToSuperview(view.leftAnchor, offset: padding)
searchButton.rightToSuperview(view.rightAnchor, offset: -padding)
searchButton.height(50)
collectionView.topToBottom(of: searchButton, offset: padding)
collectionView.edgesToSuperview(excluding: .top)
}
fileprivate func configureTextFields() {
firstNameTextField.tag = 0
lastNameTextField.tag = 1
numberTextField.tag = 2
raceTextField.tag = 3
}
fileprivate func configureButton() {
view.addSubview(searchButton)
searchButton.addTarget(self, action: #selector(searchRunners), for: .touchUpInside)
}
fileprivate func createLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .estimated(100))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)
let spacing = CGFloat(10)
group.interItemSpacing = .fixed(spacing)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = spacing
let padding = CGFloat(10)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: padding, bottom: 0, trailing: padding)
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(80))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
section.boundarySupplementaryItems = [header]
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
fileprivate func configureCollectionView() {
collectionView = UICollectionView(frame: .zero, collectionViewLayout: createLayout())
collectionView.autoresizingMask = [.flexibleHeight]
collectionView.backgroundColor = .systemBackground
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(RunnerCell.self, forCellWithReuseIdentifier: RunnerCell.reuseID)
collectionView.register(Header.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: Header.reuseID)
view.addSubview(collectionView)
}
}
extension SearchRunnerVC : UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return runners.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RunnerCell.reuseID, for: indexPath) as? RunnerCell else { fatalError("Unable to dequeue runner cell")}
cell.configure(runner: runners[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: Header.reuseID, for: indexPath) as? Header else { fatalError("Unable to dequeue header")}
header.title.text = "Résultats"
header.seeButton.addTarget(self, action: #selector(seeAllTapped), for: .touchUpInside)
header.separator.alpha = 0
header.backgroundColor = collectionView.backgroundColor
return header
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath.item)
}
}

Seems like your problem relates to the UITapGestureRecognizer you are adding to your view in configureSuperView(). The touches that would usually trigger the collectionView didSelectItemAt... delegate function are being sent to the gesture recognizer's handler, which is hideKeyboard().
Comment the line with view.addGestureRecognizer(viewTap) and your code will work. If so, I can also help you with achieving the hideKeyboard functionality, just let me know.

Do not use tap gesture on cells because
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
does excatly what you expect. The problem is probably in your collectionView configuration/setup. Could you show the whole ViewController code?

Related

How to reload Section in UICollection view

So i'm trying to implement a searchbar to search over different categories. After looking online a bit, I took a tutorial for UITableView and adapted it to my UICollectionView.
The searching works fine, as I am able to print the search results which go into the filteredCategories array.
Where I am having trouble is reloading the UICollectionView using the new filteredCategories array. I tried collectionView.reloadData() as well as collectionView.reloadSections(NSIndexSet(index: 1) as IndexSet) but with no success.
It seems as if the values are passed only once when the section is being built, and that I cannot change it.
Here is my code:
import UIKit
class ViewController: UIViewController {
var collectionView: UICollectionView!
let categories: [String] = ["Technology", "Science", "Entertainment", "Business", "Health", "Sports"]
var filteredCategories: [String] = ["none"]
var count = 0
var filteredCount = 6
var ready: [String] = ["none"]
var readyCount = 0
func filterIt() {
if isFiltering {
ready = filteredCategories
readyCount = filteredCategories.count
} else {
ready = categories
readyCount = categories.count
}
print("second", ready)
}
lazy var sections: [Section] = [
TitleSection(title: "NewsStand"),
// SearchSection(),
BasicGridSection(categories: ready, count: readyCount)
]
lazy var collectionViewLayout: UICollectionViewLayout = {
var sections = self.sections
let layout = UICollectionViewCompositionalLayout { (sectionIndex, environment) -> NSCollectionLayoutSection? in
return sections[sectionIndex].layoutSection()
}
return layout
}()
let searchController = UISearchController(searchResultsController: nil)
var isSearchBarEmpty: Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String) {
filteredCategories = categories.filter { (category: String) -> Bool in
print("filtered", filteredCategories)
return category.lowercased().contains(searchText.lowercased())
}
filterIt()
collectionView.reloadData()
setupCollectionView()
collectionView.reloadSections(NSIndexSet(index: 1) as IndexSet)
}
var isFiltering: Bool {
return searchController.isActive && !isSearchBarEmpty
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Compositional Layout"
self.view.backgroundColor = UIColor.white
self.title = "NewsStand"
setupCollectionView()
filterIt()
// 1
searchController.searchResultsUpdater = self
// 2
searchController.obscuresBackgroundDuringPresentation = false
// 3
searchController.searchBar.placeholder = "Search"
// 4
navigationItem.searchController = searchController
// 5
definesPresentationContext = true
}
func setupCollectionView() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: collectionViewLayout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.backgroundColor = UIColor.white
collectionView.register(UINib(nibName: "TitleCell", bundle: .main), forCellWithReuseIdentifier: TitleCell.identifier)
collectionView.register(UINib(nibName: "GridCell", bundle: .main), forCellWithReuseIdentifier: GridCell.identifier)
collectionView.register(UINib(nibName: "SearchCell", bundle: .main), forCellWithReuseIdentifier: SearchCell.identifier)
self.view.addSubview(collectionView)
collectionView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
collectionView.reloadData()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
collectionView.reloadData()
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
sections.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
sections[section].numberOfItems
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
sections[indexPath.section].configureCell(collectionView: collectionView, indexPath: indexPath)
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let articlesView = storyboard?.instantiateViewController(withIdentifier: "articleViewController") as? ArticleViewController else {
print("error")
return
}
// set the post id for the comments
articlesView.set(index: indexPath.item)
// present(articlesView, animated: true, completion: nil)
self.navigationController!.pushViewController(articlesView, animated: true)
}
}
extension ViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
filterContentForSearchText(searchBar.text!)
}
}
and Section:
struct BasicGridSection: Section {
// TODO: create a constant for the title of the header of type String
var categories: [String]
var numberOfItems: Int
// TODO: create an initializer to set the title
init(categories: [String], count: Int) {
self.categories = categories
self.numberOfItems = count
}
func layoutSection() -> NSCollectionLayoutSection? {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.1), heightDimension: .fractionalHeight(0.8))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(0.4))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)
let section = NSCollectionLayoutSection(group: group)
return section
}
func configureCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: GridCell.self), for: indexPath) as? GridCell
cell!.set(label:categories[indexPath.row])
return cell!
}
}
Thanks!

Header in NSCollectionView is has wrong size

I have an ViewController with a NSCollectionView with Headers and Items. At a first glance everything looks fine:
As soon as I resize the window, one header becomes the size of an item – and the other headers disappear:
This is my code:
extension ViewController:NSCollectionViewDataSource {
static let picItem = "PictureItem"
static let headerItem = "HeaderItem"
func numberOfSections(in collectionView: NSCollectionView) -> Int {
return 3
}
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return 15
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let itemView = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: ViewController.picItem), for: indexPath)
return itemView
}
func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
let headerView = collectionView.makeSupplementaryView(ofKind: kind, withIdentifier: NSUserInterfaceItemIdentifier(rawValue: ViewController.headerItem), for: indexPath)
return headerView
}
}
extension ViewController:NSCollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> NSSize {
return NSSize(width: 0, height: 20)
}
}
The storyboard:
What's going on here? What did I miss?
Additional Code
HeaderItem.swift
class HeaderItem: NSCollectionViewItem {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.green.cgColor
}
}
HeaderItem.xib
The outlet view of the HeaderItem object is the CustomView.
I think the best way is follow Apple guidelines for the new approach . First create UICollectionView Layout where specify all geometry params for header, cells and footers, for example:
import UIKit
import Combine
import Resolver
class DeliveryController: UIViewController {
var dataSource: UICollectionViewDiffableDataSource<Order, OrderItem>! = nil
func createLayout() -> UICollectionViewLayout {
let itemWidth: CGFloat = 140.0
let itemHeight: CGFloat = 140.0
let headerHeight: CGFloat = 140.0
let footerHeight: CGFloat = 68.0
let config = UICollectionViewCompositionalLayoutConfiguration()
config.interSectionSpacing = 10.0
let layout = UICollectionViewCompositionalLayout(sectionProvider: {
(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let order = self.orders[sectionIndex]
let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(
widthDimension: .absolute(itemWidth), heightDimension: .absolute(itemHeight)))
item.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 0, bottom: 0, trailing: 10)
let containerGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(widthDimension: .absolute(itemWidth), heightDimension: .estimated(itemHeight)), subitems: [item])
let section = NSCollectionLayoutSection(group: containerGroup)
section.orthogonalScrollingBehavior = UICollectionLayoutSectionOrthogonalScrollingBehavior.continuous
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(headerHeight)),
elementKind: DeliveryController.sectionHeaderElementKind,
alignment: .top)
let sectionFooter = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(footerHeight)),
elementKind: DeliveryController.sectionFooterElementKind,
alignment: .bottom)
if order.hasDriver {
section.boundarySupplementaryItems = [sectionHeader, sectionFooter]
} else {
section.boundarySupplementaryItems = [sectionHeader]
}
return section
}, configuration: config)
return layout
}
Then configureHierarchy using given layout:
func configureHierarchy(in view: UIView, top: UIView, bottom: UIView) {
collectionView = UICollectionView(frame: .zero /* create with zero frame and use constarins after we adding collectionView as subview */, collectionViewLayout: createLayout())
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.backgroundColor = .white
view.addSubview(collectionView)
collectionView.anchor(top: top.bottomAnchor, left: view.leftAnchor, bottom: bottom.topAnchor, right: view.rightAnchor, paddingTop: 8, paddingLeft: 0, paddingBottom: 2, paddingRight: 0)
collectionView.delegate = self
}
And then DataSource:
func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<OrderItemCell, OrderItem> { (cell, indexPath, item) in
cell.item = item
}
dataSource = UICollectionViewDiffableDataSource<Order, OrderItem>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, item: OrderItem) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
}
let headerRegistration = UICollectionView.SupplementaryRegistration
<OrderHeaderView>(elementKind: "Header") {
(supplementaryView, string, indexPath) in
let order = self.orders[indexPath.section]
supplementaryView.order = order
}
let footerRegistration = UICollectionView.SupplementaryRegistration
<OrderFooterView>(elementKind: "Footer") {
(supplementaryView, string, indexPath) in
let order = self.orders[indexPath.section]
supplementaryView.order = order
supplementaryView.delegate = self
}
dataSource.supplementaryViewProvider = { (view, kind, index) in
let order = self.orders[index.section]
if kind == DeliveryController.sectionHeaderElementKind {
return self.collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: index)
} else if order.hasDriver {
return self.collectionView.dequeueConfiguredReusableSupplementary(using: footerRegistration, for: index)
}
return nil
}
var snapshot = NSDiffableDataSourceSnapshot<Order, OrderItem>()
orders.forEach { order in
snapshot.appendSections([order])
snapshot.appendItems(order.items, toSection: order)
}
dataSource.apply(snapshot, animatingDifferences: false)
}
}

How to achieve a dynamic CollectionView Cell Height (Size for item at)?

I have decided start a project with no storyboard for the first time and at the moment I am stuck trying to figuring out how to achieve a proper dynamic cell in my CollectionViewController. Reading some of the solutions here in Stackoverflow I got the point in using a layout.estimatedItemSize but it somehow stops the bouncing effect from the collection view and also in my second cell which is a horizontal scroll view will not work after this implementation.
Here is my code(UICollectionViewController):
class InfoEmpaVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
fileprivate let cell1 = "cell1"
fileprivate let cell2 = "cell2"
fileprivate let cellID = "cellID"
fileprivate let headerID = "headerID"
fileprivate let padding: CGFloat = 10
//
//
//GET THE DATA FROM:
var empanada: Empanadas!
struct Cells {
static let empanadaStepsCell = "EmpanadaStepsCell"
}
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionViewLayout()
setupCollectionView()
}
//CHANGE COLOR OF STATUS BAR
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
fileprivate func setupCollectionView() {
collectionView.backgroundColor = UIColor(named: "ColorBackground")
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(InfoHeaderVC.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerID)
//FirstCELL
collectionView.register(EmpaIngredientsListCell.self, forCellWithReuseIdentifier: cell1)
//SecondCELL
collectionView.register(EmpaStepsCell.self, forCellWithReuseIdentifier: cellID)
}
fileprivate func setupCollectionViewLayout() {
if let layout = collectionViewLayout as? UICollectionViewFlowLayout {
layout.sectionInset = .init(top: padding, left: padding, bottom: padding, right: padding)
layout.estimatedItemSize = CGSize(width: view.frame.width, height: 50)
}
}
var headerView: InfoHeaderVC!
//HEADER COLLECTION VIEW
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as? InfoHeaderVC
headerView.empaImageView.image = UIImage(named: empanada.image)
headerView.empaTitleLabel.text = empanada.name
headerView.empaDescriptionLabel.text = empanada.info
headerView.buttonX.addTarget(self, action: #selector(dismissVC), for: .touchUpInside)
headerView.buttonAddFavorite.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
return headerView!
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return .init(width: view.frame.width, height: 350)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cell1, for: indexPath)
guard let cellOne = cell as? EmpaIngredientsListCell else {
fatalError("Wrong cell type for section 0. Expected CellTypeOne")
}
//INGREDIENT LIST
cellOne.empaIngredientList.ingredientList.append(contentsOf: empanada.ingredients)
cellOne.empaIngredientList.configure()
return cellOne
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! EmpaStepsCell
cell.pasos.append(contentsOf: empanada.pasos)
cell.titleHeaderLabel.text = "Step by Step"
cell.configure()
print (cell.pasos.count)
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.item == 0 {
return .init(width: view.frame.width - 2 * padding, height: 300)
} else {
return .init(width: view.frame.width - 2 * padding, height: 300)
}
}
//OBJC FUNC
#objc func dismissVC() {
dismiss(animated: true)
}
//SAVE DATA
#objc func addButtonTapped() {
configureSaveToFavorites(empanada: empanada!)
}
}
Cell 1:
import UIKit
import SnapKit
class EmpaIngredientsListCell: UICollectionViewCell {
let empaIngredientList = EmpaIngredientsContainerView()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
print(intrinsicContentSize)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
return layoutAttributes
}
func setupUI() {
//contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(empaIngredientList)
empaIngredientList.snp.makeConstraints { (make) in
make.top.bottom.left.right.equalTo(self.contentView)
make.edges.equalTo(self.safeAreaLayoutGuide)
}
}
}

collectionViewCells to collectionView position x:0, y:0

I am experiencing some layout issues with collectionViewCells. I am programmatically applying layout constraints and UICollectionViewCells are not being pinned to the 0,0 position in my collectionView. See attached screenshot for reference. Thanks in advance!
class CurrentUserPlaceDetailsVC: UIViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBar()
}
let menuBar: MenuBar = {
let mb = MenuBar()
return mb
}()
private func setupMenuBar() {
view.addSubview(menuBar)
view.addConstraintsWithFormat("H:|[v0]|", views: menuBar)
view.addConstraintsWithFormat("V:|-64-[v0(150)]", views: menuBar)
}
var placesTableVC: PlacesTableVC?
}
class MenuBar: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.green
cv.dataSource = self
cv.delegate = self
return cv
}()
let cellId = "cellId"
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
addConstraintsWithFormat("H:|[v0]|", views: collectionView)
addConstraintsWithFormat("V:|[v0]|", views: collectionView)
}
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)
cell.backgroundColor = .blue
return cell
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension UIView {
func addConstraintsWithFormat(_ format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
view.translatesAutoresizingMaskIntoConstraints = false
viewsDictionary[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
You need to set the minimumLineSpacing, headerReferenceSize, and/or sectionInset of the UICollectionViewFlowLayout instance you use to initialize your UICollectionView. You can also set the contentInset of the UICollectionView.
Try this:
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
cv.contentInset = .zero
return cv
}()

Swift 3 - CollectionView data source did not return a valid cell

I am using this code: https://www.youtube.com/watch?v=bNtsekO51iQ , but when I implement my data and use collectionView.reloadData() it crashes with error code *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the collection view's data source did not return a valid cell from -collectionView:cellForItemAtIndexPath: for index path <NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}'
class ChatLogController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var userId:Int?
var position:Int = 0
var otherAvatar:UIImage = UIImage(named: "defaultAvatar")!
var otherName:String = ""
var otherSex:String = ""
var otherBanned:Int = 0
var otherBlocked:Int = 0
var messagesDates:[String] = []
var messagesText:[String] = []
var messagesIds:[String] = []
var messagesPics:[UIImage?] = []
var messagesSeen:[Int] = []
var messagesWhoSendIt:[Int] = []
private let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.register(ChatLogMessageCell.self, forCellWithReuseIdentifier: cellId)
collectionView!.isPrefetchingEnabled = false
loadChatsFor(position: position)
} override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messagesIds.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath as IndexPath) as! ChatLogMessageCell
cell.messageTextView.text = messagesText[indexPath.row]
cell.profileImageView.image = UIImage(named: "defaultAvatar")!
let size = CGSize(width:250,height:1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messagesText[indexPath.row]).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
cell.messageTextView.frame = CGRect(x:48 + 8, y:0, width:estimatedFrame.width + 16, height:estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x:48, y:0, width:estimatedFrame.width + 16 + 8, height:estimatedFrame.height + 20)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width:250,height:1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messagesText[indexPath.row]).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
return CGSize(width:view.frame.width, height:estimatedFrame.height + 20)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(8, 0, 0, 0)
}
}
class ChatLogMessageCell: BaseCell {
let messageTextView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 18)
textView.text = "Sample message"
textView.backgroundColor = UIColor.clear
return textView
}()
let textBubbleView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 0.95, alpha: 1)
view.layer.cornerRadius = 15
view.layer.masksToBounds = true
return view
}()
let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 15
imageView.layer.masksToBounds = true
return imageView
}()
override func setupViews() {
super.setupViews()
addSubview(textBubbleView)
addSubview(messageTextView)
addSubview(profileImageView)
addConstraintsWithFormat(format:"H:|-8-[v0(30)]", views: profileImageView)
addConstraintsWithFormat(format:"V:[v0(30)]|", views: profileImageView)
profileImageView.backgroundColor = UIColor.red
}
}
extension UIView {
func addConstraintsWithFormat(format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
class BaseCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
}
}
In loadChatsFor I get the array data from web and I use collectionView.reloadData(), but when this function is performed it crashes my app. I've searched for answer, but unsuccessfully. I've added IOS 10 function collectionView!.isPrefetchingEnabled = false in view did load from this answer UICollectionView exception in UICollectionViewLayoutAttributes from iOS7, but also not working. Even the method collectionViewLayout invalidateLayout before reloadData and after reloadData doesn't stop the crash. So what else I can do to make it work ?
I am coming in this CollectionViewController from UITableViewCell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let layout = UICollectionViewFlowLayout()
let controller = ChatLogController(collectionViewLayout: layout)
controller.userId = chatUserIds[indexPath.row]
navigationController?.pushViewController(controller, animated: true)
}
I've added UICollectionViewDelegateFlowLayout in the class of the tableview
You are not implementing correct cellForItemAt method of UICollectionViewDataSource. Signature of this method is changed in Swift 3 like this.
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath as IndexPath) as! ChatLogMessageCell
cell.messageTextView.text = messagesText[indexPath.row]
cell.profileImageView.image = UIImage(named: "defaultAvatar")!
let size = CGSize(width:250,height:1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messagesText[indexPath.row]).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
cell.messageTextView.frame = CGRect(x:48 + 8, y:0, width:estimatedFrame.width + 16, height:estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x:48, y:0, width:estimatedFrame.width + 16 + 8, height:estimatedFrame.height + 20)
return cell
}