Cannot Click Custom UICollectionViewCell - ios

I have a custom UICollectionView cell with an imageView and a label, as listed below:
import UIKit
class PollCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var pollQuestion: UILabel!
}
I want to make each populated cell clickable and then pass information from that clicked cell into a new ViewController. Below is the ViewController that populates the custom cell.
import UIKit
import FirebaseDatabase
import FirebaseDatabaseUI
import FirebaseStorageUI
private let reuseIdentifier = "PollCell"
class TrendingViewController: UICollectionViewController {
var ref: FIRDatabaseReference!
var dataSource: FUICollectionViewDataSource!
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell classes
self.dataSource = self.collectionView?.bind(to: self.ref.child("Polls")) { collectionView, indexPath, snap in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PollCell
/* populate cell */
cell.pollQuestion.text = snap.childSnapshot(forPath: "question").value as! String?
let urlPollImage = snap.childSnapshot(forPath: "image_URL").value as! String?
cell.imageView.sd_setImage(with: URL(string: urlPollImage!), placeholderImage: UIImage(named: "Fan_Polls_Logo.png"))
//Comment
return cell
}
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath)
}
}
I also have some code to "invert" the cells that populate in my inverted ViewController:
import Foundation
import UIKit
class InvertedStackLayout: UICollectionViewLayout {
let cellHeight: CGFloat = 100.00 // Your cell height here...
override func prepare() {
super.prepare()
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttrs = [UICollectionViewLayoutAttributes]()
if let collectionView = self.collectionView {
for section in 0 ..< collectionView.numberOfSections {
if let numberOfSectionItems = numberOfItemsInSection(section) {
for item in 0 ..< numberOfSectionItems {
let indexPath = IndexPath(item: item, section: section)
let layoutAttr = layoutAttributesForItem(at: indexPath)
if let layoutAttr = layoutAttr, layoutAttr.frame.intersects(rect) {
layoutAttrs.append(layoutAttr)
}
}
}
}
}
return layoutAttrs
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let layoutAttr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
let contentSize = self.collectionViewContentSize
layoutAttr.frame = CGRect(
x: 0, y: contentSize.height - CGFloat(indexPath.item + 1) * cellHeight,
width: contentSize.width, height: cellHeight)
return layoutAttr
}
func numberOfItemsInSection(_ section: Int) -> Int? {
if let collectionView = self.collectionView,
let numSectionItems = collectionView.dataSource?.collectionView(collectionView, numberOfItemsInSection: section)
{
return numSectionItems
}
return 0
}
override var collectionViewContentSize: CGSize {
get {
var height: CGFloat = 0.0
var bounds = CGRect.zero
if let collectionView = self.collectionView {
for section in 0 ..< collectionView.numberOfSections {
if let numItems = numberOfItemsInSection(section) {
height += CGFloat(numItems) * cellHeight
}
}
bounds = collectionView.bounds
}
return CGSize(width: bounds.width, height: max(height, bounds.height))
}
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if let oldBounds = self.collectionView?.bounds,
oldBounds.width != newBounds.width || oldBounds.height != newBounds.height
{
return true
}
return false
}
}

You must to implement this method of UICollectionViewDelegate
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
debugPrint("this works")
}
I hope this helps you

This Function used to select the row of your collection view
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
Then you use function call. Whatever you need, just Defined the inside function. And easily called the function during the selected the row (inside the didSelectItemAt indexPath)

Make sure you have enabled UserIntraction for both UILabel and UIImageView, because both have default value as false.
self.dataSource = self.collectionView?.bind(to: self.ref.child("Polls")) { collectionView, indexPath, snap in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PollCell
/* populate cell */
cell.pollQuestion.userInteractionEnabled = true;
cell.imageView.userInteractionEnabled = true;
cell.pollQuestion.text = snap.childSnapshot(forPath: "question").value as! String?
let urlPollImage = snap.childSnapshot(forPath: "image_URL").value as! String?
cell.imageView.sd_setImage(with: URL(string: urlPollImage!), placeholderImage: UIImage(named: "Fan_Polls_Logo.png"))
//Comment
return cell
}
Then you need to use of UICollectionViewDelegate method:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
}

Add a segue in your storyboard from the cell to the view controller you want to transition to. You can use the prepare for segue method, prepare(for:sender:), to pass your data. Using the segue (UIStoryboarySegue documentation) parameter, you can access the 'destinationViewController`.
Check out the View Controller for iOS Programming Guide: Using Segues for more information about using segues.

Related

UICollection View "Reload Data" After Scrolling Crash

I have made a collection view with cells arranged in rows in columns using a great tutorial. I have added a button to a toolbar in the main view controller that calls collectionView.reloadData() as I want a user to be able to edit values which will in turn update the datasource and then reload the collection view to show the updates.
Running this on a simulator it works, but if any scrolling takes place it causes this crash *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'. If no scrolling has taken place then calling collectionView.reloadData() works. I can't find where this empty array is which is causing the crash. I have tried printing all the arrays that are used in the code in the console but none appear to be empty. Have tried commenting out various lines of code to try and narrow down where the problem is, it seems to be something in the override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? block. I have also tried reseting the collection view frame co-ordinates to 0 before reload data is called but that also didn't work. Have been stuck going round in circles for a few days which no luck. Any suggestions as to where I am going wrong would be hugely appreciated! My code so far is below (please excuse the long winded explanation and code);
View Controller
import UIKit
class ViewController: UIViewController {
// variable to contain cell indexpaths sent from collectionViewFlowLayout.
var cellIndexPaths = [IndexPath] ()
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var collectionViewFlowLayout: UICollectionViewFlowLayout! {
didSet {
collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
}
#IBAction func editButtonPressed(_ sender: UIButton) {
collectionView.reloadData()
}
#IBAction func doneButtonPressed(_ sender: UIButton) {
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
}
}
extension ViewController:UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! collectionViewCell
cell.backgroundColor = .green
cell.textLabel.text = "\(indexPath) - AAAABBBBCCCC"
return cell
}
}
extension ViewController:UICollectionViewDelegate, IndexPathDelegate {
func getIndexPaths(indexPathArray: Array<IndexPath>) {
cellIndexPaths = indexPathArray.uniqueValues
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
var cellArray = [UICollectionViewCell] ()
print(cellIndexPaths)
for indexPathItem in cellIndexPaths {
if let cell = collectionView.cellForItem(at: indexPathItem) {
if indexPathItem.section == indexPath.section {
cellArray.append(cell)
}
}
for cells in cellArray {
cells.backgroundColor = .red
Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { (timer) in
cells.backgroundColor = .green
}
}
}
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.cellForItem(at: indexPath)
if let safeCell = cell {
let cellSize = CGSize(width: safeCell.frame.width, height: safeCell.frame.height)
return cellSize
} else {
return CGSize (width: 300, height: 100)
}
}
}
extension Array where Element: Hashable {
var uniqueValues: [Element] {
var allowed = Set(self)
return compactMap { allowed.remove($0) }
}
}
Flow Layout
import UIKit
protocol IndexPathDelegate {
func getIndexPaths(indexPathArray: Array<IndexPath>)
}
class collectionViewFlowLayout: UICollectionViewFlowLayout {
override var collectionViewContentSize: CGSize {
return CGSize(width: 10000000, height: 100000)
}
override func prepare() {
setupAttributes()
indexItemDelegate()
}
// MARK: - ATTRIBUTES FOR ALL CELLS
private var allCellAttributes: [[UICollectionViewLayoutAttributes]] = []
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
for rowAttrs in allCellAttributes {
for itemAttrs in rowAttrs where rect.intersects(itemAttrs.frame) {
layoutAttributes.append(itemAttrs)
}
}
return layoutAttributes
}
// MARK: - SETUP ATTRIBUTES
var cellIndexPaths = [IndexPath] ()
private func setupAttributes() {
allCellAttributes = []
var xOffset: CGFloat = 0
var yOffset: CGFloat = 0
for row in 0..<rowsCount {
var rowAttrs: [UICollectionViewLayoutAttributes] = []
xOffset = 0
for col in 0..<columnsCount(in: row) {
let itemSize = size(forRow: row, column: col)
let indexPath = IndexPath(row: row, column: col)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = CGRect(x: xOffset, y: yOffset, width: itemSize.width, height: itemSize.height).integral
rowAttrs.append(attributes)
xOffset += itemSize.width
cellIndexPaths.append(indexPath)
}
yOffset += rowAttrs.last?.frame.height ?? 0.0
allCellAttributes.append(rowAttrs)
}
}
// MARK: - CONVERT SECTIONS TO ROWS, ITEMS TO COLUMNS
private var rowsCount: Int {
return collectionView!.numberOfSections
}
private func columnsCount(in row: Int) -> Int {
return collectionView!.numberOfItems(inSection: row)
}
// MARK: - GET CELL SIZE
private func size(forRow row: Int, column: Int) -> CGSize {
guard let delegate = collectionView?.delegate as? UICollectionViewDelegateFlowLayout,
let size = delegate.collectionView?(collectionView!, layout: self, sizeForItemAt: IndexPath(row: row, column: column)) else {
assertionFailure("Implement collectionView(_,layout:,sizeForItemAt: in UICollectionViewDelegateFlowLayout")
return .zero
}
return size
}
private func indexItemDelegate () {
let delegate = collectionView?.delegate as? IndexPathDelegate
delegate?.getIndexPaths(indexPathArray: cellIndexPaths)
}
}
// MARK: - INDEX PATH EXTENSION
//creates index path with rows and columns instead of sections and items
private extension IndexPath {
init(row: Int, column: Int) {
self = IndexPath(item: column, section: row)
}
}
Collection Cell
import UIKit
class collectionViewCell: UICollectionViewCell {
#IBOutlet weak var textLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
contentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentView.leftAnchor.constraint(equalTo: leftAnchor),
contentView.rightAnchor.constraint(equalTo: rightAnchor),
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
}
I have managed to get around the issue by using the UICollectionView.reloadSections(sections: IndexSet) method. This doesn't cause any crashes. I loop through all sections and add each section to an IndexSet variable then use that in the reload sections method like this;
var indexSet = IndexSet()
let rowCount = collectionView.numberOfSections
for row in 0..<rowCount {
indexSet = [row]
collectionView.reloadSections(indexSet)
}

cellForItemAt called only once in Swift collectionView

If I use flow layout with collectionView, then all my cells are visible with the data. If I use a custom layout, then cellForItemAt is only accessed for index (0,0), and correspondingly only a single cell is displayed.
I'm baffled why - please help!
Minimal example below:
ViewController:
import UIKit
private let reuseIdentifier = "customCell"
class customCollectionViewController: UICollectionViewController {
#IBOutlet var customCollectionView: UICollectionView!
let dwarfArray = ["dopey", "sneezy", "bashful", "grumpy", "doc", "happy", "sleepy"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dwarfArray.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! customCollectionViewCell
let cellContentsIndex = indexPath.row
if cellContentsIndex <= dwarfArray.count
{
cell.displayContent(name: dwarfArray[cellContentsIndex])
}
return cell
}
}
Custom Cell
import UIKit
class customCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var nameLabel: UILabel!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(frame: CGRect){
super.init(frame: frame)
}
public func displayContent(name: String){
nameLabel.text = name
}
func setup(){
self.layer.borderWidth = 1.0
self.layer.borderColor = UIColor.black.cgColor
}}
Custom Layout
If this is not here - I can see all the cells I expect (albeit without my preferred layout). When I use this, I only see one cell.
import UIKit
class customCollectionViewLayout: UICollectionViewLayout {
let CELL_SIZE = 100.0
var cellAttrsDictionary = Dictionary<NSIndexPath, UICollectionViewLayoutAttributes>()
//define the size of the area the user can move around in within the collection view
var contentSize = CGSize.zero
var dataSourceDidUpdate = true
func collectionViewContentSize() -> CGSize{
return self.contentSize
}
override func prepare() {
if (collectionView?.numberOfItems(inSection: 0))! > 0 {
/// cycle through each item of the section
for item in 0...(collectionView?.numberOfItems(inSection: 0))!-1{
/// build the collection attributes
let cellIndex = NSIndexPath(item: item, section: 0)
let xPos = Double(item)*CELL_SIZE
let yPos = 40.0
let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndex as IndexPath)
cellAttributes.frame = CGRect(x: xPos, y:yPos, width: CELL_SIZE, height: CELL_SIZE)
// cellAttributes.frame = CGRect(x: xPos, y:yPos, width: CELL_WIDTH + 2*CELL_SPACING, height: CELL_HEIGHT)
cellAttributes.zIndex = 1
//save
cellAttrsDictionary[cellIndex] = cellAttributes
}
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
/// create array to hold all the elements in our current view
var attributesInRTect = [UICollectionViewLayoutAttributes]()
/// check each element to see if they should be returned
for cellAttributes in cellAttrsDictionary.values {
if rect.intersects(cellAttributes.frame)
{
attributesInRTect.append(cellAttributes)
}
}
return attributesInRTect
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cellAttrsDictionary[indexPath as NSIndexPath]!
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}}
Output
The problem is with contentSize value
func collectionViewContentSize() -> CGSize{
return self.contentSize
}
Just replace func collectionViewContentSize()... by something like this:
func lastLayoutAttributes() -> UICollectionViewLayoutAttributes? {
return cellAttrsDictionary.values.map { $0 }.sorted(by: { $0.frame.maxX < $1.frame.maxX }).last
}
override var collectionViewContentSize: CGSize {
guard let collectionView = collectionView else { return .zero }
guard collectionView.frame != .zero else { return .zero }
let width: CGFloat
let height: CGFloat = collectionView.frame.height
if let lastLayoutAttributes = lastLayoutAttributes() {
width = lastLayoutAttributes.frame.maxX
} else {
width = 0
}
return CGSize(width: width, height: height)
}
And you will see more than one cell.

Why my collection view disappear when updating the stepper value?

here is my simplified code in my view controller
class WishListVC: UIViewController {
#IBOutlet weak var wishListCollectionView: UICollectionView!
private var products = [Product]()
private var selectedProduct : Product?
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: - cell Delegate
extension WishListVC : ListProductCellDelegate {
func addToCartButtonDidTapped(at selectedIndexPath: IndexPath, collectionView: UICollectionView) {
guard let userOrder = userOrder else {return}
let selectedProduct = products[selectedIndexPath.item]
Order.addProductToOrderRealmDatabase(userOrder: userOrder, selectedProduct: selectedProduct)
wishListCollectionView.reloadData()
updateBadgeOnCartTabBar()
}
func stepperButtonDidTapped(at selectedIndexPath: IndexPath, stepperValue: Int, collectionView: UICollectionView) {
guard let userOrder = userOrder else {return}
let selectedProduct = products[selectedIndexPath.item]
if stepperValue > 0 {
Product.changeProductQuantityInRealmDatabase(selectedProduct: selectedProduct, quantity: stepperValue)
} else {
Order.removeProductFromOrderRealmDatabase(userOrder: userOrder, selectedProduct: selectedProduct)
Product.changeProductQuantityInRealmDatabase(selectedProduct: selectedProduct, quantity: 0)
}
wishListCollectionView.reloadData()
updateBadgeOnCartTabBar()
}
}
//MARK: - Collection View Data Source
extension WishListVC : UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return products.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: WishListStoryboardData.CollectionViewIdentifiers.productSliderCell.rawValue, for: indexPath) as? ListProductCell else { return UICollectionViewCell()}
cell.productData = products[indexPath.item]
cell.delegate = self
cell.collectionView = wishListCollectionView
return cell
}
}
and here is the code for my collection view cell:
protocol ListProductCellDelegate {
func addToCartButtonDidTapped(at selectedIndexPath: IndexPath, collectionView : UICollectionView)
func stepperButtonDidTapped( at selectedIndexPath: IndexPath, stepperValue: Int, collectionView : UICollectionView)
}
class ListProductCell: UICollectionViewCell {
#IBOutlet weak var productImageViewAspectRatio: NSLayoutConstraint!
#IBOutlet weak var addToCartButton: UIButton!
#IBOutlet weak var stepper: GMStepper!
var collectionView : UICollectionView?
var delegate: ListProductCellDelegate?
var productData : Product? {
didSet {
updateUI()
}
}
#IBAction func addToCartButtonDidPressed(_ sender: UIButton) {
guard let collectionView = collectionView else {return}
guard let selectedIndexPath = collectionView.indexPathForView(view: sender) else {return}
self.delegate?.addToCartButtonDidTapped(at: selectedIndexPath, collectionView: collectionView)
}
#IBAction func stepperDidTapped(_ sender: GMStepper) {
guard let collectionView = self.collectionView else {return}
guard let selectedIndexPath = collectionView.indexPathForView(view: sender) else {return}
self.delegate?.stepperButtonDidTapped(at: selectedIndexPath, stepperValue: Int(sender.value), collectionView: collectionView)
}
private func updateUI() {
guard let product = productData else {return}
stepper.value = Double(product.quantity)
setLikeButton(product: product)
setCartAndStepperButton()
}
private func setCartAndStepperButton() {
guard let selectedProduct = productData else {return}
func showStepperButton(status: Bool) {
// to decide whether to show stepper or add to cart button.
stepper.isHidden = !status
stepper.isEnabled = status
addToCartButton.isHidden = status
addToCartButton.isEnabled = !status
}
if selectedProduct.quantity == 0 {
showStepperButton(status: false)
} else {
showStepperButton(status: true)
}
}
}
I don't understand why after I tap the stepper for the first time after the 'Add To Cart' disappear, the collection view will disappear.
I don't have collectionView.isHidden in my entire code, but I don't know why my collection view disappear like the file .gif below
http://g.recordit.co/NAEc36MbrM.gif
but if the stepper is already show with some stepper value more than 1, then it will make my collection view dissapear like the gif below
http://recordit.co/SLdqf1ztFZ.gif
the minimum stepper value is set to be 1.
If I change the collection view reload data wishListCollectionView.reloadData() in the stepperButtonDidTapped method above to be just reload data in certain cell only using wishListCollectionView.reloadItems(at: [selectedIndexPath]) the problem will be solved, but the stepper value seems it will be updated little slower, and it looks laggy.
I don't know how to trace the last line that will be executed so it makes my collection view disappears.
and if I reload the data in the main thread using:
DispatchQueue.main.async {
self.wishListCollectionView.reloadData()
}
it won't make the collection view disappear, but If I edit the cell index 4 it will affect the cell index 1 like gif here: http://g.recordit.co/6802BJDdtx.gif
I change the number in the fifth cell but it will automatically change the second cell.
Note to:
Use Xcode Ui Debugging tool and check if your collectionView is hidden or empty
Realm is realTime database ,any changes you make in database will be applied
on your arrays too(like products array)
The reason of stepper problem is becouse of :
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: WishListStoryboardData.CollectionViewIdentifiers.productSliderCell.rawValue, for: indexPath) as? ListProductCell else { return UICollectionViewCell()}
and your using stepperButtonDidTapped delegete inside of cell.
declure your cells once and store them inside array,like below:
var cellArray:[ListProductCell]=[]
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if (cellArray.count > indexPath.row){
return cellArray[indexPath.row]
}else{
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: WishListStoryboardData.CollectionViewIdentifiers.productSliderCell.rawValue, for: indexPath) as? ListProductCell else { return UICollectionViewCell()}
cell.productData = products[indexPath.item]
cell.delegate = self
cell.collectionView = wishListCollectionView
cellArray.append(cell)
return cell
}}
My case load get data success but not see data when reload.
This problem case born set Height in func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize is less than height cell on storyboard.

didSelectItem not being called

My didSelectItemAt method is not being called and nothing is being printed into the console. I have user interaction turned on and I still can not get it to print out anything. I am not sure if my custom PinterestStyle Layout is causing this or if I am missing something. The ultimate goal would be to segue into a detail view controller showing the profile page of the cell selected. I will do that using prepareForSegue however I still can't even get it to print out the name of the cell when tapped.
class PagesCollectionViewController: UICollectionViewController, firebaseHelperDelegate {
var storageRef: StorageReference!{
return Storage.storage().reference()
}
var usersList = [String]()
var authService : FirebaseHelper!
var userArray : [Users] = []
var images: [UIImage] = []
var names: [String] = []
override func viewWillAppear(_ animated: Bool) {
if Global.Location != "" && Global.Location != nil
{
usersList = Global.usersListSent
print(usersList)
self.authService.ListOfUserByLocation(locationName: Global.Location, type: .ListByLocation)
}
}
override func viewDidLoad() {
self.collectionView?.allowsSelection = true
self.collectionView?.isUserInteractionEnabled = true
super.viewDidLoad()
self.authService = FirebaseHelper(viewController: self)
self.authService.delegate = self
setupCollectionViewInsets()
setupLayout()
}
private func setupCollectionViewInsets() {
collectionView!.backgroundColor = .white
collectionView!.contentInset = UIEdgeInsets(
top: 20,
left: 5,
bottom: 49,
right: 5
)
}
private func setupLayout() {
let layout: PinterestLayout = {
if let layout = collectionViewLayout as? PinterestLayout {
return layout
}
let layout = PinterestLayout()
collectionView?.collectionViewLayout = layout
return layout
}()
layout.delegate = self
layout.cellPadding = 5
layout.numberOfColumns = 2
}
func firebaseCallCompleted(data: AnyObject?, isSuccess: Bool, error: Error?, type: FirebaseCallType) {
if(type == .ListByLocation) {
if(isSuccess) {
self.userArray.removeAll()
self.images.removeAll()
self.images.removeAll()
if(data != nil) {
let dataDict = data as! NSDictionary
let keyArray = dataDict.allKeys
for i in 0 ..< keyArray.count {
var dict = NSDictionary()
dict = dataDict.object(forKey: keyArray[i]) as! NSDictionary
self.userArray.append(Users.init(data: dict))
}
}
self.collectionView?.reloadData()
}
else {
print(error?.localizedDescription)
SVProgressHUD.dismiss()
}
}
}
}
extension PagesCollectionViewController {
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userArray.count
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(userArray[indexPath.row].name)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: "PagesCollectionViewCell",
for: indexPath) as! PagesCollectionViewCell
cell.nameLabel.text = userArray[indexPath.row].name
if let imageOld = URL(string: userArray[indexPath.row].photoURL){
cell.photo.sd_setImage(
with: imageOld,
placeholderImage: nil,
options: [.continueInBackground, .progressiveDownload]
)
}
return cell
}
}
extension PagesCollectionViewController : PinterestLayoutDelegate {
func collectionView(collectionView: UICollectionView,
heightForImageAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat {
var image: UIImage?
let url = URL(string: userArray[indexPath.row].photoURL)
let data = try? Data(contentsOf: url!)
image = UIImage(data: data!)
return (image?.height(forWidth: withWidth))!
}
func collectionView(collectionView: UICollectionView,
heightForAnnotationAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat {
return 30
}
}
Check 2 conditions:-
Make sure you have set delegate to UICollectionView
Make sure Content in PageCollectionCell like image having no user interaction enabled. If image user interaction is enabled then didSelectItemAt will not call.
as Manish Mahajan said a quick fix would be:
in cellForItemAt func set contentView as not clickable
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.contentView.isUserInteractionEnabled = false
return cell
}

Stuck understanding how to create a table with multiple columns in iOS Swift

I've spent the better half of the day so far researching and trying to understand how to make a table with multiple columns. Embarrassingly, I am still quite new to Swift and programming in general so a lot of the stuff I've read and found aren't helping me too much.
I have basically found exactly what I want to create with this gentleman's blo:
http://www.brightec.co.uk/blog/uicollectionview-using-horizontal-and-vertical-scrolling-sticky-rows-and-columns
However, even with his Github I'm still confused. It seems as if he did not use Storyboard at all (and for my project I've been using storyboard a lot). Am I correct in assuming this?
What I have so far is a UICollectionView embedded in a navigation controller. From here, I have created a new cocoa touch class file subclassed in the CollectionView. But from here is where I'm not entirely sure where to go.
If I can have some direction as to where to go from here or how to properly set it up that would be GREATLY appreciated.
Thanks so much in advance!
IOS 10, XCode 8, Swift 3.0
I found an awesome tutorial on this. thanks to Kyle Andrews
I created a vertical table which can be scrollable on both directions by subclassing UICollectionViewLayout. Below is the code.
class CustomLayout: UICollectionViewLayout {
let CELL_HEIGHT: CGFloat = 50
let CELL_WIDTH: CGFloat = 180
var cellAttributesDictionary = Dictionary<IndexPath, UICollectionViewLayoutAttributes>()
var contentSize = CGSize.zero
override var collectionViewContentSize: CGSize {
get {
return contentSize
}
}
var dataSourceDidUpdate = true
override func prepare() {
let STATUS_BAR_HEIGHT = UIApplication.shared.statusBarFrame.height
let NAV_BAR_HEIGHT = UINavigationController().navigationBar.frame.size.height
collectionView?.bounces = false
if !dataSourceDidUpdate {
let yOffSet = collectionView!.contentOffset.y
for section in 0 ..< collectionView!.numberOfSections {
if section == 0 {
for item in 0 ..< collectionView!.numberOfItems(inSection: section) {
let cellIndexPath = IndexPath(item: item, section: section)
if let attrs = cellAttributesDictionary[cellIndexPath] {
var frame = attrs.frame
frame.origin.y = yOffSet + STATUS_BAR_HEIGHT + NAV_BAR_HEIGHT
attrs.frame = frame
}
}
}
}
return
}
dataSourceDidUpdate = false
for section in 0 ..< collectionView!.numberOfSections {
for item in 0 ..< collectionView!.numberOfItems(inSection: section) {
let cellIndexPath = IndexPath(item: item, section: section)
let xPos = CGFloat(item) * CELL_WIDTH
let yPos = CGFloat(section) * CELL_HEIGHT
let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndexPath)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)
// Determine zIndex based on cell type.
if section == 0 && item == 0 {
cellAttributes.zIndex = 4
} else if section == 0 {
cellAttributes.zIndex = 3
} else if item == 0 {
cellAttributes.zIndex = 2
} else {
cellAttributes.zIndex = 1
}
cellAttributesDictionary[cellIndexPath] = cellAttributes
}
}
let contentWidth = CGFloat(collectionView!.numberOfItems(inSection: 0)) * CELL_WIDTH
let contentHeight = CGFloat(collectionView!.numberOfSections) * CELL_HEIGHT
contentSize = CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributesInRect = [UICollectionViewLayoutAttributes]()
for cellAttrs in cellAttributesDictionary.values {
if rect.intersects(cellAttrs.frame) {
attributesInRect.append(cellAttrs)
}
}
return attributesInRect
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cellAttributesDictionary[indexPath]
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
Below is my CollectionViewController Code.
import UIKit
private let reuseIdentifier = "Cell"
class VerticalCVC: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.isScrollEnabled = true
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 20
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CustomCell
if indexPath.section == 0 {
cell.backgroundColor = UIColor.darkGray
cell.titleLabel.textColor = UIColor.white
} else {
cell.backgroundColor = UIColor.white
cell.titleLabel.textColor = UIColor.black
}
cell.titleLabel.text = "section: \(indexPath.section) && row: \(indexPath.row)"
return cell
}
}
To force CollectionView to use Custom Layout instead of UICollectionViwFlowLayout check below image.
Result:
Portrait mode
landscape mode
One approach is to use a custom cell in a tableviewcontroller. Your story board consists of a table in which the cell is a custom cell with UILabels for columns laid out next to each other (with properly defined constraints).
Example code for the controllers looks like:
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as TableViewCell
cell.column1.text = "1" // fill in your value for column 1 (e.g. from an array)
cell.column2.text = "2" // fill in your value for column 2
return cell
}
}
and:
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet weak var column1: UILabel!
#IBOutlet weak var column2: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
In IB I set up a tableview and added a stackview in the content view (can be done programmatically). The labels are setup programmatically since it allows me to set the width of each column as a fraction of the cell width. Also, I acknowledge that some of the calculations inside the table view cellForRow method should be moved out.
import UIKit
class tableViewController: UITableViewController {
var firstTime = true
var width = CGFloat(0.0)
var height = CGFloat(0.0)
var cellRect = CGRectMake(0.0,0.0,0.0,0.0)
let colors:[UIColor] = [
UIColor.greenColor(),
UIColor.yellowColor(),
UIColor.lightGrayColor(),
UIColor.blueColor(),
UIColor.cyanColor()
]
override func viewDidLoad() {
super.viewDidLoad()
// workaround to get the cell width
cellRect = CGRectMake(0, 0, self.tableView.frame.size.width ,44);
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
var cellWidth = CGFloat(0.0)
var cellHeight = CGFloat(0.0)
let widths = [0.2,0.3,0.3,0.2]
let labels = ["0","1","2","3"]
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let v = cell.contentView.subviews[0] // points to stack view
// Note: using w = v.frame.width picks up the width assigned by xCode.
cellWidth = cellRect.width-20.0 // work around to get a right width
cellHeight = cellRect.height
var x:CGFloat = 0.0
for i in 0 ..< labels.count {
let wl = cellWidth * CGFloat(widths[i])
let lFrame = CGRect(origin:CGPoint(x: x,y: 0),size: CGSize(width:wl,height: cellHeight))
let label = UILabel(frame: lFrame)
label.textAlignment = .Center
label.text = labels[i]
v.addSubview(label)
x = x + wl
print("i = ",i,v.subviews[i])
v.subviews[i].backgroundColor = colors[i]
}
return cell
}
}

Resources