U23r
U23r

Reputation: 1703

How to show a message when collection view is empty

I`m trying to show a message only when my collection view is empty. Well, I try set return 1 when my if is true but when I have this, it only show one item in my collection view (even if I had more). And when I return this (code bellow) it shows all the items I have in my collection view but when I try to delete it, the last one is not "deletable", I mean, the last one stays there. How can I show this message only if I have no items in my collection view?

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

    if self.movies.count > 0 {

        self.favCollectionView.backgroundView = nil

        return self.movies.count
    }
    else {
        let rect = CGRect(x: 0,
                          y: 0,
                          width: self.favCollectionView.bounds.size.width,
                          height: self.favCollectionView.bounds.size.height)
        let noDataLabel: UILabel = UILabel(frame: rect)
        noDataLabel.text = "No favorite movies yet."
        noDataLabel.textAlignment = .center
        noDataLabel.textColor = UIColor.gray
        noDataLabel.sizeToFit()

        self.favCollectionView.backgroundView = noDataLabel

        return 0
    }
}

Update

I did it like this:

func numberOfSections(in collectionView: UICollectionView) -> Int {
    return 2
}


func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    if section == 0 { return self.movies.count}
    else if section == 1 {
        if self.movies.count < 1 { return 1 }
        else { return 0  }
    }
    return 0
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = favCollectionView.dequeueReusableCell(withReuseIdentifier: "FavCollectionCell", for: indexPath) as! FavoritesCollectionViewCell

    if indexPath.section == 0 {

        let movie = movies[indexPath.row]

        let imgStg: String = movie.posterURL!
        let imgURL: URL? = URL(string: imgStg)
        let imgSrc = ImageResource(downloadURL: imgURL!, cacheKey: imgStg)

        cell.favTitleLabel.text = movie.title

        cell.favPosterImageView.layer.cornerRadius = cell.favPosterImageView.frame.size.width/2
        cell.favPosterImageView.clipsToBounds = true

        //image cache with KingFisher
        cell.favPosterImageView.kf.setImage(with: imgSrc)

        return cell

    }

    else {

        let rect = CGRect(x: 0, y: 0, width: self.favCollectionView.frame.width, height: self.favCollectionView.frame.height)
        let noDataLabel: UILabel = UILabel(frame: rect)
        noDataLabel.text = "No favorite movies yet."
        noDataLabel.textAlignment = .center
        noDataLabel.textColor = UIColor.gray
        noDataLabel.sizeToFit()

        let cell = UICollectionViewCell()
        cell.contentView.addSubview(noDataLabel)

        return cell

    }

}

For @Lamar's solution, but the app crashed with error: enter image description here

Upvotes: 25

Views: 21378

Answers (5)

Samman Bikram Thapa
Samman Bikram Thapa

Reputation: 1067

I make use of the backgroundView in an extension as such:

extension UICollectionView {

    func setEmptyMessage(_ message: String) {
        let messageLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height))
        messageLabel.text = message
        messageLabel.textColor = .black
        messageLabel.numberOfLines = 0;
        messageLabel.textAlignment = .center;
        messageLabel.font = UIFont(name: "TrebuchetMS", size: 15)
        messageLabel.sizeToFit()

        self.backgroundView = messageLabel;
    }

    func restore() {
        self.backgroundView = nil
    }
}

and I use it as such:

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

    if (self.movies.count == 0) {
        self.collectionView.setEmptyMessage("Nothing to show :(")
    } else {
        self.collectionView.restore()
    }

    return self.movies.count
}

Upvotes: 62

adamvx
adamvx

Reputation: 157

Maybe little bit late but here is little more constraint based solution. Still may help some one.

First create some empty state message (You can also create own, more complex view with image or something).

    lazy var emptyStateMessage: UILabel = {
        let messageLabel = UILabel()
        messageLabel.translatesAutoresizingMaskIntoConstraints = false
        messageLabel.textColor = .darkGray
        messageLabel.numberOfLines = 0;
        messageLabel.textAlignment = .center;
        messageLabel.font = UIFont.systemFont(ofSize: 15)
        messageLabel.sizeToFit()
        return messageLabel
    }()

Then add two methods and call them whenever you like.

    func showEmptyState() {
        collectionView.addSubview(emptyStateMessage)
        emptyStateMessage.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor).activate()
        emptyStateMessage.centerYAnchor.constraint(equalTo: collectionView.centerYAnchor).activate()
    }

    func hideEmptyState() {
        emptyStateMessage.removeFromSuperview()
    }

Upvotes: 6

Lamour
Lamour

Reputation: 3040

I would say have two sections in your collectionView, where one is for the actual data to be displayed, then the other one when you don't have any data.

Assuming we are populating data in section 0.

in numberOfRowsInSection you'd have something like:

     if section == 0 { return movies.count}
     else if section == 1 {
          if movies.count < 1 { return 1 }
          else { return 0  }
     }
        return 0

in your cellForItemAt you'd do something like that:

 if indexPath.section == 0 {
      // let cell = ...
      // show your data into your cell 
        return cell  
     }else {
          // here you handle the case where there is no data, but the big thing is we are not dequeuing  
          let rect = CGRect(x: 0, y: 0, width: self.favCollectionView.frame.width, height: self.favCollectionView.frame.height)
        let noDataLabel: UILabel = UILabel(frame: rect)
        noDataLabel.text = "No favorite movies yet."
        noDataLabel.textAlignment = .center
        noDataLabel.textColor = UIColor.gray
        noDataLabel.sizeToFit()

        let cell = UICollectionViewCell()
        cell.contentView.addSubview(noDataLabel)

       return cell
      }

Upvotes: 0

iWheelBuy
iWheelBuy

Reputation: 5679

Why don't you use:

open var backgroundView: UIView?

// will be automatically resized to track the size of the collection view and placed behind all cells and supplementary views.`

You can show it whenever collection is empty. Example:

var collectionView: UICollectionView!
var collectionData: [Any] {
    didSet {
        collectionView.backgroundView?.alpha = collectionData.count > 0 ? 1.0 : 0.0
    }
}

Upvotes: 0

esh
esh

Reputation: 2872

Use backgroundView of your Collection View to display the no results message.

Upvotes: 3

Related Questions