CollectionView didSelectItem does not respond. Why? - ios

first of all, i did set the delegates. every other protocol is working with collection views.
I tested it with the sizeFotItem function. and that worked just fine. But how come that cellForItem function does not respond at all?
Anyone know why?
here is my code;
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.delegate = self
self.collectionView.dataSource = self
let flow = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layoutSettings(flow)
playButtonPressed(self)
}
And this is in my extension of my viewController;
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 9
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! CollectionCell
print(menu.menuArray[indexPath.row])
cell.cellText.text = menu.menuArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Do something")
}
}
The funny part is.. I have almost the same code in an other project, and every is working fine.
Only difference is that the project has an collectionView INSIDE a TableView.
Hopefully one of you find an obvious reason why this is not working. I'm gladly to know why :)
thanks.
PS. Is it normal that the editor doesn't recognize the "extension ViewController" part as code?

Solved by just deleting the CollectionViewCell and making a new one.
I still don't know what was wrong with my previous collectionViewCell, because every protocol was working just fine except for didSelectItemAt..
If anybody had a familiar problem and knows what was going on, I'm still happy to hear what was going on ;)

Related

Swift UICollectionView Cell being rendered twice

I am writing a customer calendar as a challenge for me to practice UICollectionView. This issue has been puzzled me for few days. So the problem is when it reuses the cell, the load data source function has been called twice or three times, then it leads to:
Sometimes it's called three times and my calendar days become 100 110 etc.
Here is the delegate and datasource code:
extension CalendarViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.calendarPages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CalendarCell
cell.calendarPage = self.calendarPages[self.currentPageIndex]
cell.contentView.addSubview(self.buildCalendarPage(index: indexPath.row, frame: cell.contentView.frame)) // this is where it add new month page to the calendar, and it's randomly called multiple times
print("cell called")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
I know what's going one there, so basically I made it too complicated, there is a calendar UICollectionView in another UICollectionView, (I just want to make it scrollable), so each time when the cell is reused, I forget to reload the UI
Solution, keep only one UICollectionView, each cell is a day, and create another UICollectionViewCell class to control the reusable cell, thanks for the Babar's help

How to use custom UICollectionReusableView as section header of collection view?

I've been driven insane for hours as I can't get around with the issue.
I have a collection view which can have different section with different no. of items in each. For each section I need to use a section header of different type. So for this, I'm going to use UICollectionReusableView. But I can't seem to succeed in using a custom subclass of UICollectionReusableView by means of UINib registration.
A crash happens when I downcast the reusable view to my subclass. Like:
let friendHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind,
withReuseIdentifier: "FriendHeaderView",
for: indexPath) as! FriendHeaderView
Below is the code snippet:
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private let viewModel = ProfileViewModel()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
// more code
collectionView.register(UINib(nibName: "FriendHeaderView", bundle: nil),
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: "FriendHeaderView")
}
}
Now here is the data source implementation:
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
// valid implementation
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// valid implementation
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// valid implementation
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
let friendHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "FriendHeaderView", for: indexPath) as! FriendHeaderView
// *** Crash happens here *** //
return friendHeader
default:
assert(false, "Invalid element type")
}
}
}
And I don't know why the collectionView(_:layout:referenceSizeForHeaderInSection:) needs to be also implemented. So here it is:
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let size = CGSize(width: collectionView.bounds.width, height: 100)
return size
}
}
Okay, now come to the point: The above mentioned crash doesn't happen
at all if I don't downcast with as! operator. Well, if I use section
header from the storyboard instead of UINib registration, there is
no crash.
If I'm going to need multiple type header, then I can't also use storyboard approach or without down-casting approach as I need to feed data to those headers.
What can I do to have multiple type headers with view built from interface builder?
I've made a demo project with what I've said above. If anyone is interested please check out that.
Once you assign proper class and identifier in your Xib file, then it will work without crashes.
Well, after some more investigation and the input from #good4pc in the accepted answer (actually I found out that by myself before looking at the answer) it seems that the issue is actually happening for some unwanted issue with Xcode.
When we create any view (preferably, UITableViewCell or UICollectionViewCell) with .xib, the class identity is provided automatically for that .xib in the identity inspector. But this was not the case for UICollectionReusableView. See the attached screenshot below for easy understanding.
This is a UICollectionViewCell subclassed with .xib:
This is a UICollectionReusableView subclassed with .xib:
So the key is to provide the class identity of the .xib file which
is done from the attributes inspector.

Which item was selected in a UICollectionView, if there 're multiple UICollectionViews

I have more than one collectionView in a ViewController. The cell of those collectionViews has the same format.. so I'm reusing them. So my question is: How to identify in the method
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath)
I don't want to do a couple of if's
I've found this solution everywhere, but really don't like it. Here is the code
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
if let aCell = cell as? ItemCollectionViewCell{
aCell.setupCell(with: self.items[indexPath.item])
}
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == self.colletionViewTwo{
// goto viewController1
}else if collectionView == self.colletionViewOne{
// goto viewController2
}
}
Create two classes that implement the collection view delegate and data source and use one of each. So you'll have these two extra objects in your current view controller.
Seeing your code now, the above is probably too heavy. Alternatively, add a dictionary in which you store the collection view as key and a selector as value. This is extensible as you say you want.
To be honest, what's your issue an if (or switch) statement like you have now?

Collection View delete item in depending on its location

I have created a collection view that pulls from an array of images (8 right now, but user can add more). I originally was using a scrollview, but found it easier with a collection, and thanks to this great community, went to a collection view. I need to find the indexPath to delete an item at a given point. So here is some code I have so far, but I am new to this specifically. Here is some code I currently have.
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var myCollectionView: UICollectionView!
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! UserFeedCollectionViewCell
cell.myImage.image = UIImage(named: imageArray[indexPath.row])
cell.myImage.layer.cornerRadius = 12.0
cell.myImage.clipsToBounds = true
return cell
}
//delete item at current item - 2
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.row > 2 {
myCollectionView.deleteItems(at: [])
}
}
Hope this will help and feel free to ask in the comments if you have a question.
Edit: Paging is enabled, and it is horizontal scrolling, and each image takes up the whole cell.
How about keeping an array of tapped on image indices?
Define a variable for the indexes at the top:
var selected = [IndexPath]()
Then implement didSelectItemAt as:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selected.append(indexPath)
if selected.count > 2 {
let ndx = selecte.count - 3
let twoBack = selected[ndx]
myCollectionView.deleteItems(at:[twoBack])
}
}
The above would work fine for the first selection, but at that point, you would need to figure out how you handle the next selection - whether you wipe the selected array and start over, or need to keep track of further selections to handle the next input.
Depending on how you want to proceed, the selected array would either need to be wiped or to be modified to remove the item that was deleted.

Is completely static UICollectionView possible?

At UITableView, completely static tableView config is possible. You can disconnect UITableView's datasource and put each cell on storyboard(or xib) by using IB.
I tried same thing with UICollectionView. disconnect UICollectionView's datasource. Put each cell on UICollectionView on storyboard. I built it without any errors. But it didin't work. cells were not displayed at all.
Is UICollectionView without datasource possible?
No.
Creating a static UICollectionViewController is not allowed. You must have a data source delegate.
I also want to point out that there is not a static UITableView, but a static UITableViewController. It's a difference.
You can easily create a static UICollectionViewController.
Just create every cell in interface builder, give them re-use identifiers(e.g. "Home_1" "Home_2" "Home_3"), and populate the methods as follows:
class HomeViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellIdentifiers:[String] = ["Home_1","Home_2","Home_3"]
let sizes:[CGSize] = [CGSize(width:320, height:260),CGSize(width:320, height:160),CGSize(width:320, height:100)]
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellIdentifiers.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifiers[indexPath.item], for: indexPath)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return sizes[indexPath.item]
}
}
Then set the view controller to be of the proper class, and, hey presto, a (basically) static collection. I'm sorry to say but this is BY FAR the best way to support portrait and landscape views when you have groups of controls...
I did a little experimenting and wanted to add my own method since it helped me achieve the truly static, highly custom Collection View I was looking for.
You can create custom UICollectionViewCells for each cell you want to display in your Collection View, and register them with all the Cell IDs in your Collection View, like this:
Create your static cell:
class MyRedCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .red
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Make as many of these as you want.
And then back in your Collection View Controller, register them with their corresponding cellId:
let cellIds = ["redCell","blueCell","greenCell"]
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(MyRedCell.self, forCellWithReuseIdentifier: "redCell")
collectionView.register(MyBlueCell.self, forCellWithReuseIdentifier: "blueCell")
collectionView.register(MyGreenCell.self, forCellWithReuseIdentifier: "greenCell")
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIds[indexPath.item], for: indexPath)
return cell
}
Each cell will display exactly what's in its class.

Resources