Swift 3 API renaming in subclasses - ios

I've encountered some unexpected behaviour in my code where UICollectionViewDelegate callbacks are only received if I use the pre-Swift 3 method signatures.
To demonstrate this issue, I have created two view controller subclasses:
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet var collectionView: UICollectionView!
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
}
// func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// print(#function)
// }
}
class SubViewController: ViewController {
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
print(#function)
}
func collectionView(_ collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: IndexPath) {
print(#function)
}
}
The callback of interest is the collectionView(_:willDisplay:forItemAt:) method.
The first thing to note is that if I add the old variant of that method (collectionView(_:willDisplayCell:forItemAtIndexPath:)) to ViewController, the compiler helpfully warns me that the method has been renamed:
'collectionView(_:willDisplayCell:forItemAtIndexPath:)' has been renamed to 'collectionView(_:willDisplay:forItemAt:)'
However adding the same method to SubViewController produces no warning – it seems to treat it like it would any other previously-undefined method.
If I run the app with an instance of ViewController (uncomment the collectionView(_:willDisplay:at:) method there, and comment it out in SubViewController), the correct delegate method (with the new method name) is called. If I instead use the class SubViewController, the incorrect delegate method (with the old method name) is called.
Is this the correct behaviour? If so, why? It makes it somewhat difficult to reason about which method signatures I should use, especially when there seems to be no compile-time checking of the method that is actually called in SubViewController.

As pointed out in one of the comments the solution to this problem is to add #objc in front of the function like this.
#objc (collectionView:willDisplayCell:forItemAtIndexPath:)
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// code goes here
}

Related

Non-void function should return a value

I am currently trying to make a calendar and this error keeps on popping up.
I have tried return 0, and return UICollectionViewCell.
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {return 0}
private func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {return }
Non-void function should return a value
If you want to return 0 and UICollectionViewCell,
you should return them
Like this
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "nameOfIdentifier", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {return 0}
First of all, if you're using a UICollectionView, you might need to show atleast 1 UICollectionViewCell in it.
And for that very purpose there is UICollectionViewDataSource methods.
Returning 0 in collectionView(_: numberOfItemsInSection:) won't do anything useful. Its just like I've a collectionView and don't want to show anything in it (until and unless there are specific conditions to return 0).
So, the method must return the number of cells you want to show in the collectionView.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
Now comes the error you're facing: Non-void function should return a value.
The error is because of another UICollectionViewDataSource's methods, i.e. collectionView(_: cellForItemAt:). This method expects you to return the actual collectionViewCell that'll be visible in the collectionView.
In the code you added, you're only calling return. Instead you must return an instance of UICollectionViewCell like so,
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YOUR_CELL_IDENTIFIER", for: indexPath)
return cell
}

Implement delegate in didSelectItemAt

I am using delegate to make a connection between a UICollectionViewCell and a UICollectionViewController. In this connection I want to say, if user clicks on a UIView its super class is changed, I already did it by using gesture.
The only problem is, I think I have to implement this delegation into the didSelectItemAt protocol of UIcollectionView', which I am not sure to how to do it.
For example, first I did it in the cellForItemAt, which was a mistake, buy I could implement easily.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as? ListCell
cell?.selectionDelegate = self // implement the delegate
}
but I don't know how to do the same thing in didSelectItemAt, because I think I should do it here, not in cellForItemAt
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
Thank you so much for your help in advance
Let's say you had your original selection delegate:
protocol SelectionDelegate {
func didSelect(_ cell: ListCell)
}
Then you can easily implement collectionView's didSelect by just calling your selection delegate:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? ListCell else { return }
didSelect(cell)
}

Swift: Click onto cell of UICollectionView and open AlertViewController

In my app, I am using an UICollectionView. Now I want to develop an UIAlertController, when clicking onto any cell of the collection view.
I started with following code:
extension HomeViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
…
}
// specify cells
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
….
}
// called when widget is moved
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
…
}
// called when clicked
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print("Got clicked!")
}
}
But somehow, "Got clicked!" is never printed.
try next:
extension HomeViewController: UICollectionViewDataSource, UICollectionViewDelegate {
}
or
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
...
cell.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(_:))))
}
func tap(_ sender: UITapGestureRecognizer) {
let location = sender.location(in: self.collectionView)
let indexPath = self.collectionView.indexPathForItem(at: location)
if let index = indexPath {
print("Got clicked on index: \(index)!")
}
}
This is the version using the delegate:
extension HomeViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("item at \(indexPath.section)/\(indexPath.item) tapped")
}
}
Instead of using the extension you can also just add UICollectionViewDelegate and the collectionView(...didSelectItemAt:...) function to the class HomeViewController directly:
class HomeViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("item at \(indexPath.section)/\(indexPath.item) tapped")
}
}
This is because you might have placed some UIButton in the cell. Tap on some empty area in the cell then you will get the 'Got click on index'

How to reload collection view inside UICollectionReusableView in swift

I have a UICollectionView (let name aCollection) inside a View controller and i use a UICollectionReusableView to show header at the top of collection view
I also have a another UICollectionView (let name bCollection) in UICollectionReusableView. I need to show here top user list. but when i am trying to connect an outlet from storyboard i am getting an error
I know how to reload data in collection view
self.aCollection.reloadData()
My issue is how to connect bCollection outlet and how to reload bCollection to show user list coming from web services?
To take the outlet of bCollection, you need to create a subclass of UICollectionReusableView.
Example:
UIViewController containing aCollection:
class ViewController: UIViewController, UICollectionViewDataSource
{
#IBOutlet weak var aCollectionView: UICollectionView!
override func viewDidLoad()
{
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
return collectionView.dequeueReusableCell(withReuseIdentifier: "aCell", for: indexPath)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
{
return (collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "reusableView", for: indexPath) as! ReusableView)
}
}
UICollectionReusableView containing bCollection:
class ReusableView: UICollectionReusableView, UICollectionViewDataSource
{
#IBOutlet weak var bCollectionView: UICollectionView!
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
return collectionView.dequeueReusableCell(withReuseIdentifier: "bCell", for: indexPath)
}
}
Interface Screenshot
Edit:
To reload bCollection:
You need a reference to the reusableView that you are using. The way you are using it ReusableView() is not right.
Use it like this:
var reusableView: ReusableView?
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
{
self.reusableView = (collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "reusableView", for: indexPath) as! ReusableView) //Storing the reference to reusableView
return self.reusableView!
}
Now, to reload bCollection,
self.reusableView?.bCollectionView.reloadData()
Since your UICollectionView (bCollection) is inside the UICollectionReusableView you can connect outlet to UICollectionReusableView's class only. So I believe you may have to create a custom class for UICollectionReusableView and assign it in the storyboard and connect bCollection 's outlet to that custom class

Swift Collectionview didSelectItemAtIndexPath not working

I have UICollectionView with custom cell (xib file), I manage to display the collectionview but I don't manage to detect when I tap on a cell with the function :
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){
Usually, I manage to do that, but this time I don't know why it doesn't work. Actually I'm using this pods "https://cocoapods.org/pods/SACollectionViewVerticalScalingFlowLayout"
My code is :
class ProjectsController: UIViewController {
#IBOutlet weak var collectionViewGridFormat: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionViewGridFormat.registerNib(UINib(nibName: "ProjectsGridFormatCell", bundle: nil), forCellWithReuseIdentifier: "cellProjectGrid")
...
}
}
extension ProjectsController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 30
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell : ProjectsGridFormatCell = collectionView.dequeueReusableCellWithReuseIdentifier("cellProjectGrid", forIndexPath: indexPath) as! ProjectsGridFormatCell
cell.lblProjectName.text = "Test project"
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){
print("aa")
}
}
But when I tap on a cell, "print("aa")" is not displayed.
Have I given enough information ? :)
Thanks for your help !! :D
Regards,
Is "User Interaction Enabled" checked under the Identity Inspector in Storyboard?

Resources