Swift 3 UICollectionView Error - swift

In my iOS app, I have a UICollectionView controller with the following code inside:
var images = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
loadImages()
// Register cell classes
}
func loadImages() {
images.append(UIImage(named: "ActivityTab")!)
self.collectionView?.reloadData()
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return images.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! EventCollectionViewCell
let image = images[indexPath.row]
cell.imageView.image = image
return cell
}
And I have a UICollectionViewCell (EventCollectionViewCell) with the following code:
#IBOutlet weak var imageView: UIImageView!
override func prepareForReuse() {
super.prepareForReuse()
self.imageView.image = nil
}
I have linked everything up with the storyboard too. This looks like it should work but I get the following error:
Could not cast value of type 'UICollectionViewCell' (0x1092c04a8) to EventCollectionViewCell'
Does anyone know what the error is and how to fix it. It only shows when I return images.count in the number of sections.
I am using xcode 9 but have tried with xcode 8 and the error still persists. Any help would be greatly appreciated. :)

Let change this line of code:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! EventCollectionViewCell
to:
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? EventCollectionViewCell else { return UICollectionViewCell() }

Check for the following things:
EventCollectionViewCell is a subclass of UICollectionViewCell.
Have you registered EventCollectionViewCell with the collectionView?
EventCollectionViewCell has the same Reusable Identifier that you are using while calling dequeueReusableCell with "reuseIdentifier".
What is the value of reuseIdentifier here?

Related

How to get all collectionview cell index inside tableview cell using Protocol in Swift

I am using collectionview inside tableview cell. so when collectionview cell button is clicked then present viewcontroller i am using protocol..
code for tableviewcell and delegate:
protocol CustomCellDelegate: class {
func sharePressed(cell: ProposalTableVIewCell)
}
class ProposalTableVIewCell: UITableViewCell, UICollectionViewDelegate,UICollectionViewDataSource {
#IBOutlet weak var attetchmentsCollectionview: UICollectionView!
var delegate: CustomCellDelegate?
public var bidAttatchment: Array<Get_attachments>?
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return bidAttatchment?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AttatchmentCollectionViewCell", for: indexPath) as! AttatchmentCollectionViewCell
let attatchBid = bidAttatchment?[indexPath.item]
cell.attatchmentLbl.text = attatchBid?.filename
cell.openBtn.tag = indexPath.item
cell.openBtn.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
return cell
}
#objc func connected(sender: UIButton){
delegate?.sharePressed(cell: self)
}
code for viewcontroller: when i press sharePressed getting only collectionview's first cell value.. how to get all cells value.. please do let me know
class ViewMyAppliedReqVC: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, CustomCellDelegate{
func sharePressed(cell: ProposalTableVIewCell) {
guard let index = tableView.indexPath(for: cell)?.row else { return }
let name = getBitDetails?.result?.bid?.get_attachments?[index].filename// always getting only first cell value
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ViewProposalTableVIewCell1", for: indexPath) as! ViewProposalTableVIewCell1
cell.bidAttatchment = getBitDetails?.result?.bid?.get_attachments
cell.delegate = self
cell.attetchmentsCollectionview.reloadData()
return cell
}
You are always getting same index because you are taking out index of "ProposalTableVIewCell" and this is tableView cell. And collectionView cells are in the same tableView cell.
Solution:
Take another parameter in protocol function like below for storing index of collection cell
protocol CustomCellDelegate: class {
func sharePressed(cell: ProposalTableVIewCell,collectionCellIndex:Int)
}
func sharePressed(cell: ProposalTableVIewCell, collectionCellIndex:Int) {
guard let index = tableView.indexPath(for: cell)?.row else { return }
let name = getBitDetails?.result?.bid?.get_attachments?[collectionCellIndex].filename// This will return index of collection cell
}
#objc func connected(sender: UIButton){
delegate?.sharePressed(cell: self,collectionCellIndex: sender.tag )
}

How to access an instance of UIStackView inside a cell in swift?

I have a stackView(called 'btnStack') which holds four buttons in a cell(class 'PresentationCell') which I am trying to hide with the following code:
func hideBtnStack() {
let cell = collectionView.visibleCells.first as! PresentationCell
cell.btnStack.isHidden = true
}
However, when I run the code I get the following error:
'Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value'
Is there an easier/better way to access the btnStack in the cell?
The relevant code in cellForRowAt is:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! PresentationCell
}
I also have the btnStack declared in the PresentationCell class as follows:
class PresentationCell: UICollectionViewCell {
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var btnStack: UIStackView!
}
And I first call the hideBtnStack method in the ViewDidLoad as follows:
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
setUpCollectionView()
hideNavBar()
hideBtnStack()
}
Thanks in advance for any help that you can provide.
CjCoax gave me the clue on how to resolve this. It looks like if the hideBtnStack is declared in the viewDidLoad along with the methods for collectionView it executes before the collectionView can be populated. I resolved this by putting a delay around the hideBtnStack method and now it works.
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
setUpCollectionView()
hideNavBar()
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.hideBtnStack()
}
}
I think this solution may help you! try this one.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? PresentationCell else { return UICollectionViewCell() }
if indexPath.item == 0 {
// hide your view
cell.btnStack.isHidden = true
}
}
NOTE: try to avoid from force unwrapping.
I hope this solution works for you.

Refreshing or reloading data on just single object inside a collectionViewCell

i'm trying to update just one single object inside my costumViewCell,
i've tried collectionView.reloadItems(at: [IndexPath]), but this method updates my entire cell, which results to a very jittering animations.
here is a sample code of my collectionView cell,
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var buttonA: UIButton!
#IBOutlet weak var buttonB: UIButton!
var myButtonTitle: String? {
didSet{
if let title = myButtonTitle {
self.buttonA.setTitle(title, for: .normal)
}
}
}
var buttonActionCallBack: (()->()?)
override func awakeFromNib() {
super.awakeFromNib()
self.animation()
buttonA.addTarget(self, action: #selector(buttonACallBack), for: .touchUpInside)
}
#objc fileprivate func buttonACallBack() {
self.buttonActionCallBack?()
}
fileprivate func animation() {
UIView.animate(withDuration: 1.0) {
self.buttonA.transform = CGAffineTransform(translationX: 20, y: 20)
self.buttonB.transform = CGAffineTransform(translationX: 20, y: 20)
}
}
}
here is my DataSource method.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MyCollectionViewCell
let item = mainList[indexPath.row]
collectionView.reloadItems(at: <#T##[IndexPath]#>)
cell.buttonActionCallBack = {
() in
//Do Stuff and Update Just ButtonA Title
}
return cell
}
cheers.
The jittering animation occurs because of this collectionView.reloadItems(at: [IndexPath]) line written inside cellForItemAt which is really wrong approach because cellForItemAt called many a times leads to infinite loop of reloading IndexPath's. Instead of that, you just reload only that part which is necessary when action occurs.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MyCollectionViewCell
let item = mainList[indexPath.row]
//collectionView.reloadItems(at: <#T##[IndexPath]#>) #removed
cell.buttonActionCallBack = {
() in
//Do Stuff and Update Just ButtonA Title
collectionView.reloadItems(at: [indexPath]) //Update after the change occurs to see the new UI updates
}
return cell
}

CollectionView pass data

What is the correct way to pass data from my ViewController to my CollectionViewItem?
ViewContoller
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = CollectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CollectionViewItem"), for: indexPath) as! CollectionViewItem
item.themeName = getDirectoryContentByIndex(index: indexPath)
return item
}
CollectionViewItem
class CollectionViewItem: NSCollectionViewItem {
var themeName: String!
#IBOutlet weak var NameOfTheme: NSTextFieldCell!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
Edit:
I want to assign the themeNameas stringValue of NameOfTheme. But if I set the value, the IBOutlet is nil.
I recommend you to make fields of your cell private and make configure(YOUR_PARAMS) (in our case configure(themeName: String). So in cellForRow you will call this function cell.configure(themeName: "")

Toggle image in UICollectionViewCell when cell selected

I have a UICollectionView of items, and I would like an image in a cell to be toggled when the user selects the cell.
I have a custom UICollectionViewCell:
class RDCell: UICollectionViewCell {
var textLabel: UILabel!
var imageView: UIImageView!
var isSelected: Bool!
...(do init and all that good stuff)
}
And selctected item in collection view :
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
let celld = (collectionView.dequeueReusableCellWithReuseIdentifier("collectionCell", forIndexPath: indexPath) as? RDCell)!
let indexPaths = collectionView.indexPathsForSelectedItems()
let newCell = collectionView.cellForItemAtIndexPath(indexPath) as! RDCell
if(celld.selected){
var image: UIImage = UIImage(named: "notSelected")!
newCell.imageView.image = image
newCell.selected = false
}else{
var image: UIImage = UIImage(named: "selected")!
newCell.imageView.image = image
newCell.selected = true
}
return true
}
My attempt partially works, after selecting and unselecting one item I am not able to select it again after, I need to select a different cell before returning to select the first, and this same bug happens on all selected cells.
Any suggestions or another way to implement the functionality I seek would be greatly appreciated.
Swift 4
I just use the property "highlightedImage" in the "cellForItemAt indexPath" method of the collectionView and set another image on it.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = UIColor.clear
let imageView = cell.viewWithTag(1) as! UIImageView
imageView.image = imagesArray1[indexPath.row]
imageView.highlightedImage = imagesArray2[indexPath.row]
imageView.contentMode = .scaleAspectFill
return cell
}
In my case, i assigned a tag in the imageView and call it through it.
Best regards.
You should never call cellForItemAtIndexPath directly. You have no guarantee of what cell you're getting and the changes you make may have no effect.
The proper way to do this is to track your state within the class and change the state of the cell in cellForItemAtIndexPath. Then you simply call collectionView?.reloadData() when you need to update the views.
Simple example for reference:
var selectionTracking: [[Bool]] = []
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
if let selected = selectionTracking[indexPath.section][indexPath.row] {
return selected
}
else{
selectionTracking[indexPath.section][indexPath.row] = false
}
return true
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if let selected = selectionTracking[indexPath.section][indexPath.row] {
selectionTracking[indexPath.section][indexPath.row] = !selectionTracking[indexPath.section][indexPath.row]
}
else{
selectionTracking[indexPath.section][indexPath.row] = true
}
collectionView?.reloadData()
return true
}
My approach will be different for this, i would have used the didDeselectItemAt method of the collectionView...
Swift 3:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let newCell = collectionView.cellForItem(at: indexPath)
newCell.imageView.image = imageAfterSelection[indexPath.row]
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let newCell = collectionView.cellForItem(at: indexPath)
newCell.imageView.image = imagesAfterDeselection[indexPath.row]
}
Swift 4/5:
Inside your collectionViewCell class define a variable called imageName
then override isSelected property to set the image name based on selected state and in collectionView cellForItemAt method set the value for imageName variable for each cell.
CollectionViewCell Class:
class YourCollectionViewCell: UICollectionViewCell
{
#IBOutlet var cellIcon: UIImageView!
var imageName = ""
override var isSelected: Bool {
didSet {
if self.isSelected {
self.cellIcon.image = UIImage(named: "\(self.imageName) Highlighted")
} else {
self.cellIcon.image = UIImage(named: "\(self.imageName) Unhighlighted")
}
}
}
}
CollectionView DataSource:
class YourCollectionVewController: UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// this line is an extension don't copy paste
let cell = collectionView.dequeueReusableCell(with: YourCollectionViewCell.self, for: indexPath)
let imageName = "yourImageName Unhighlighted"
cell.cellIcon.image = UIImage(named: imageName)
cell.imageName = imageName
return cell
}
}