Supermafete
Supermafete

Reputation: 123

swift conditional function return

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

Answers (5)

Supermafete
Supermafete

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

Martin R
Martin R

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

DeFrenZ
DeFrenZ

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

erdekhayser
erdekhayser

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

Midhun MP
Midhun MP

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

Related Questions