Reputation: 123
I would like to know how to manage conditional returns in swift. For instance, I'm returning a custom UICollectionViewCell depending on which collectionview delegate is called:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if (collectionView.isEqual(collectionView1)) {
var cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell1", forIndexPath: indexPath) as Cell1
return cell
}
else if (collectionView.isEqual(collectionView2)) {
var cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell2", forIndexPath: indexPath) as Cell2
return cell
}
}
The compiler says "Missing return statement in a function expeted to return UICollectionViewCell", even in both cases I'm returning a cell.
I solved it adding
return UICollectionViewCell()
at the bottom of the function, but I don't think it's the correct way.
I know I can declare the cell above the first 'if', modify it and return it at the end of the function outside the 'if', but then the 'dequeueReusableCellWithIdentifier' call hangs.
Thank you all.
Upvotes: 5
Views: 16962
Reputation: 123
I find out the solution! More easy than it seems. Only must replace the "else if" by an "else":
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if (collectionView.isEqual(collectionView1)) {
var cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell1", forIndexPath: indexPath) as Cell1
return cell
}
else (collectionView.isEqual(collectionView2)) {
var cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell2", forIndexPath: indexPath) as Cell2
return cell
}
}
It works now. Thank you all guys!
Upvotes: 0
Reputation: 540075
The compiler cannot know that collectionView
will always be collectionView1
or collectionView2
in your program and therefore it gives an error message.
What you can do is to add an else
case to make the compiler happy.
If everything does well, the else
case will never be executed. If there is a logic
error in your program and both if
conditions do not match, then (in the
"Debug" configuration) the program will abort with an error message.
if (collectionView.isEqual(collectionView1)) {
let cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell1", forIndexPath: indexPath) as Cell1
// set cell properties ...
return cell
}
else if (collectionView.isEqual(collectionView2)) {
let cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell2", forIndexPath: indexPath) as Cell2
// set cell properties ...
return cell
}
else {
assertionFailure("unexpected collectionView")
return UICollectionViewCell()
}
Alternatively (and this is only a slight variant of the previous two answers),
declare the cell
as an implicitly unwrapped optional outside the if
blocks:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell : UICollectionViewCell!
if (collectionView.isEqual(collectionView1)){
cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell1", forIndexPath: indexPath) as Cell1
// set cell properties ...
} else if (collectionView.isEqual(collectionView2)){
cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell2", forIndexPath: indexPath) as Cell2
// set cell properties ...
}
return cell
}
If none of the conditions match then nil
is returned which also gives a
runtime exception.
Upvotes: 3
Reputation: 2262
First of all, to everyone answering, please not use var
.
Secondly, of course that is a correct error compiler-wise since the delegate method doesn't ensure that collectionView
is either one of your defined ones, and requires that you return a valid cell so if you want to keep in the code both cases explicitly then you need to define a valid but never used case too.
Also note that casting your cells to the proper subclass is useless here since they still are instantiated as the right class and still returned as a UICollectionViewCell
as the delegate method signatures suggests.
Here's a Swifter way of doing it:
/// define this in any .swift file included in your project
func ~=<T: AnyObject>(lhs: T, rhs: T) -> Bool {
return lhs === rhs
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let reuseIdentifier = cellReuseIdentifierForCollectionView(collectionView)
return epgCollectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
}
private func cellReuseIdentifierForCollectionView(collectionView: UICollectionView) -> String {
switch collectionView {
case collectionView1:
return "Cell1"
case collectionView2:
return "Cell2"
default:
return "" // this never happens but is still a bit of a code smell
}
}
Upvotes: 2
Reputation: 6657
To explain @MidhunMP's answer, right now your code is able to end without any return value. For example look at this code, which is similar to yours:
func myFunc() -> Int {
let myNumber = random() % 3
if myNumber == 0 {
return 0
}
else if myNumber == 1 {
return 1
}
}
What if myNumber
is 2? The function ends without any return value, and this cannot happen.
Either move the return statement to the end of the code, or add an else
clause. Both ensure that your function will return a value under all circumstances.
You will need either:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = UICollectionViewCell()
if (collectionView.isEqual(collectionView1)){
cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell1", forIndexPath: indexPath) as Cell1
} else if (collectionView.isEqual(collectionView2)){
cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell2", forIndexPath: indexPath) as Cell2
}
return cell
}
or,
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = UICollectionViewCell()
if (collectionView.isEqual(collectionView1)){
cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell1", forIndexPath: indexPath) as Cell1
return cell
} else if (collectionView.isEqual(collectionView2)){
cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell2", forIndexPath: indexPath) as Cell2
return cell
} else {
return cell;
}
}
However, use the first one because it is more elegant and it is easier to understand its meaning.
Upvotes: 6
Reputation: 107231
Since you need to return a UICollectionViewCell
, it would be better if you create a single var for that and return it (I'm not a fan of writing multiple return statement in a method) So you can change it to something like:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
var cell = UICollectionViewCell()
if (collectionView.isEqual(collectionView1))
{
cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell1", forIndexPath: indexPath) as Cell1
}
else if (collectionView.isEqual(collectionView2))
{
cell = self.epgCollectionView.dequeueReusableCellWithReuseIdentifier("Cell2", forIndexPath: indexPath) as Cell2
}
return cell
}
Upvotes: 1