Handling an empty UITableView. Print a friendly message

IphoneIosUitableviewUiview

Iphone Problem Overview


I have a UITableView that in some cases it is legal to be empty. So instead of showing the background image of the app, I would prefer to print a friendly message in the screen, such as:

> This list is now empty

What is the simplest way to do it?

Iphone Solutions


Solution 1 - Iphone

UITableView's backgroundView property is your friend.

In viewDidLoad or anywhere that you reloadData you should determine if there your table is empty or not and update the UITableView's backgroundView property with a UIView containing a UILabel or just set it to nil. That's it.

It is of course possible to make UITableView's data source do double duty and return a special "list is empty" cell, it strikes me as a kludge. Suddenly numberOfRowsInSection:(NSInteger)section has to compute the number of rows of other sections it wasn't asked about to make sure they are empty too. You also need to make a special cell that has the empty message. Also don't forget that you need to probably change the height of your cell to accommodate the empty message. This is all doable but it seems like band-aid on top of band-aid.

Solution 2 - Iphone

Same as Jhonston's answer, but I preferred it as an extension:

import UIKit

extension UITableView {

    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
        self.separatorStyle = .none
    }

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

Usage:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if things.count == 0 {
        self.tableView.setEmptyMessage("My Message")
    } else {
        self.tableView.restore()
    }

    return things.count
}

Solution 3 - Iphone

Based on the answers here, here is a quick class I made that you can use on in your UITableViewController.

import Foundation
import UIKit

class TableViewHelper {
    
    class func EmptyMessage(message:String, viewController:UITableViewController) {
        let rect = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: self.view.bounds.size.width, height: self.view.bounds.size.height))
        let messageLabel = UILabel(frame: rect)
        messageLabel.text = message
        messageLabel.textColor = UIColor.blackColor()
        messageLabel.numberOfLines = 0;
        messageLabel.textAlignment = .Center;
        messageLabel.font = UIFont(name: "TrebuchetMS", size: 15)
        messageLabel.sizeToFit()
        
        viewController.tableView.backgroundView = messageLabel;
        viewController.tableView.separatorStyle = .None;
    }
}

In your UITableViewController you can call this in numberOfSectionsInTableView(tableView: UITableView) -> Int

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    if projects.count > 0 {
        return 1
    } else {
        TableViewHelper.EmptyMessage("You don't have any projects yet.\nYou can create up to 10.", viewController: self)
        return 0
    }
}

enter image description here

With a little help from http://www.appcoda.com/pull-to-refresh-uitableview-empty/

Solution 4 - Iphone

I recommend the following library: DZNEmptyDataSet

The easiest way to add it in your project is to use it with Cocaopods like so: pod 'DZNEmptyDataSet'

In your TableViewController add the following import statement (Swift):

import DZNEmptyDataSet

Then make sure your class conforms to the DNZEmptyDataSetSource and DZNEmptyDataSetDelegate like so:

class MyTableViewController: UITableViewController, DZNEmptyDataSetSource, DZNEmptyDataSetDelegate

In your viewDidLoad add the following lines of code:

tableView.emptyDataSetSource = self
tableView.emptyDataSetDelegate = self
tableView.tableFooterView = UIView()

Now all you have to do to show the emptystate is:

//Add title for empty dataset
func titleForEmptyDataSet(scrollView: UIScrollView!) -> NSAttributedString! {
	let str = "Welcome"
	let attrs = [NSFontAttributeName: UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)]
	return NSAttributedString(string: str, attributes: attrs)
}

//Add description/subtitle on empty dataset
func descriptionForEmptyDataSet(scrollView: UIScrollView!) -> NSAttributedString! {
	let str = "Tap the button below to add your first grokkleglob."
	let attrs = [NSFontAttributeName: UIFont.preferredFontForTextStyle(UIFontTextStyleBody)]
	return NSAttributedString(string: str, attributes: attrs)
}

//Add your image
func imageForEmptyDataSet(scrollView: UIScrollView!) -> UIImage! {
	return UIImage(named: "MYIMAGE")
}

//Add your button 
func buttonTitleForEmptyDataSet(scrollView: UIScrollView!, forState state: UIControlState) -> NSAttributedString! {
	let str = "Add Grokkleglob"
	let attrs = [NSFontAttributeName: UIFont.preferredFontForTextStyle(UIFontTextStyleCallout)]
	return NSAttributedString(string: str, attributes: attrs)
}

//Add action for button
func emptyDataSetDidTapButton(scrollView: UIScrollView!) {
	let ac = UIAlertController(title: "Button tapped!", message: nil, preferredStyle: .Alert)
	ac.addAction(UIAlertAction(title: "Hurray", style: .Default, handler: nil))
	presentViewController(ac, animated: true, completion: nil)
}

These methods aren't mandatory, it's also possible to just show the empty state without a button etc.

For Swift 4

// MARK: - Deal with the empty data set
// Add title for empty dataset
func title(forEmptyDataSet _: UIScrollView!) -> NSAttributedString! {
    let str = "Welcome"
    let attrs = [NSAttributedStringKey.font: UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)]
    return NSAttributedString(string: str, attributes: attrs)
}

// Add description/subtitle on empty dataset
func description(forEmptyDataSet _: UIScrollView!) -> NSAttributedString! {
    let str = "Tap the button below to add your first grokkleglob."
    let attrs = [NSAttributedStringKey.font: UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)]
    return NSAttributedString(string: str, attributes: attrs)
}

// Add your image
func image(forEmptyDataSet _: UIScrollView!) -> UIImage! {
    return UIImage(named: "MYIMAGE")
}

// Add your button
func buttonTitle(forEmptyDataSet _: UIScrollView!, for _: UIControlState) -> NSAttributedString! {
    let str = "Add Grokkleglob"
    let attrs = [NSAttributedStringKey.font: UIFont.preferredFont(forTextStyle: UIFontTextStyle.callout), NSAttributedStringKey.foregroundColor: UIColor.white]
    return NSAttributedString(string: str, attributes: attrs)
}

// Add action for button
func emptyDataSetDidTapButton(_: UIScrollView!) {
    let ac = UIAlertController(title: "Button tapped!", message: nil, preferredStyle: .alert)
    ac.addAction(UIAlertAction(title: "Hurray", style: .default, handler: nil))
    present(ac, animated: true, completion: nil)
}

Solution 5 - Iphone

One way of doing it would be modifying your data source to return 1 when the number of rows is zero, and to produce a special-purpose cell (perhaps with a different cell identifier) in the tableView:cellForRowAtIndexPath: method.

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSInteger actualNumberOfRows = <calculate the actual number of rows>;
    return (actualNumberOfRows  == 0) ? 1 : actualNumberOfRows;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSInteger actualNumberOfRows = <calculate the actual number of rows>;
    if (actualNumberOfRows == 0) {
        // Produce a special cell with the "list is now empty" message
    }
    // Produce the correct cell the usual way
    ...
}

This may get somewhat complicated if you have multiple table view controllers that you need to maintain, because someone will eventually forget to insert a zero check. A better approach is to create a separate implementation of a UITableViewDataSource implementation that always returns a single row with a configurable message (let's call it EmptyTableViewDataSource). When the data that is managed by your table view controller changes, the code that manages the change would check if the data is empty. If it is not empty, set your table view controller with its regular data source; otherwise, set it with an instance of the EmptyTableViewDataSource that has been configured with the appropriate message.

Solution 6 - Iphone

I have been using the titleForFooterInSection message for this. I don't know if this is suboptimal or not, but it works.

-(NSString*)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section   {
    
    NSString *message = @"";
    NSInteger numberOfRowsInSection = [self tableView:self.tableView numberOfRowsInSection:section ];
    
    if (numberOfRowsInSection == 0) {
        message = @"This list is now empty";
    }
    
    return message;
}

Solution 7 - Iphone

I can only recommend to drag&drop a UITextView inside the TableView after the cells. Make a connection to the ViewController and hide/display it when appropriate (e.g. whenever the table reloads).

enter image description here

Solution 8 - Iphone

So for a safer solution:

extension UITableView {
func setEmptyMessage(_ message: String) {
    guard self.numberOfRows() == 0 else {
        return
    }
    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.systemFont(ofSize: 14.0, weight: UIFontWeightMedium)
    messageLabel.sizeToFit()
    
    self.backgroundView = messageLabel;
    self.separatorStyle = .none;
}

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

public func numberOfRows() -> Int {
    var section = 0
    var rowCount = 0
    while section < numberOfSections {
        rowCount += numberOfRows(inSection: section)
        section += 1
    }
    return rowCount
  }
}

and for UICollectionView as well:

extension UICollectionView {
func setEmptyMessage(_ message: String) {
    guard self.numberOfItems() == 0 else {
        return
    }
    
    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.systemFont(ofSize: 18.0, weight: UIFontWeightSemibold)
    messageLabel.sizeToFit()
    self.backgroundView = messageLabel;
}

func restore() {
    self.backgroundView = nil
}

public func numberOfItems() -> Int {
    var section = 0
    var itemsCount = 0
    while section < self.numberOfSections {
        itemsCount += numberOfItems(inSection: section)
        section += 1
    }
    return itemsCount
  }
}

More Generic Solution:

    protocol EmptyMessageViewType {
      mutating func setEmptyMessage(_ message: String)
      mutating func restore()
    }
    
    protocol ListViewType: EmptyMessageViewType where Self: UIView {
      var backgroundView: UIView? { get set }
    }

    extension UITableView: ListViewType {}
    extension UICollectionView: ListViewType {}

    extension ListViewType {
      mutating 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: 16)
        messageLabel.sizeToFit()

        backgroundView = messageLabel
    }

     mutating func restore() {
        backgroundView = nil
     }
}

Solution 9 - Iphone

Using the backgroundView is fine, but it does not scroll nicely like in Mail.app.

I did something similar to what xtravar did.

I added a view outside the view hierarchy of the tableViewController. hierarchy

Then i used the following code in tableView:numberOfRowsInSection::

if someArray.count == 0 {
    // Show Empty State View
    self.tableView.addSubview(self.emptyStateView)
    self.emptyStateView.center = self.view.center
    self.emptyStateView.center.y -= 60 // rough calculation here
    self.tableView.separatorColor = UIColor.clear
} else if self.emptyStateView.superview != nil {
    // Empty State View is currently visible, but shouldn't
    self.emptyStateView.removeFromSuperview()
    self.tableView.separatorColor = nil
}

return someArray.count

Basically I added the emptyStateView as a subview of the tableView object. As the separators would overlap the view, I set their color to clearColor. To get back to the default separator color, you can just set it to nil.

Solution 10 - Iphone

Using a Container View Controller is the right way to do it according to Apple.

I put all my empty state views in a separate Storyboard. Each under it's own UIViewController subclass. I add content directly under their root view. If any action/button is needed, you now already have a controller to handle it.
Then its just a matter of instantiating the desired view controller from that Storyboard, add it as a child view controller and add the container view to the tableView's hierarchy (sub view). Your empty state view will be scrollable as well, which feels good and allow you to implement pull to refresh.

Read chapter 'Adding a Child View Controller to Your Content' for help on how to implement.

Just make sure you set the child view frame as (0, 0, tableView.frame.width, tableView.frame.height) and things will be centered and aligned properly.

Solution 11 - Iphone

This is the best and simple solution.

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 60)];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 60)];
label.text = @"This list is empty";
label.center = self.view.center;
label.textAlignment = NSTextAlignmentCenter;
[view addSubview:label];
    
self.tableView.backgroundView = view;

Solution 12 - Iphone

There is a specific use case for multiple data sets and sections, where you need an empty state for each section.

You can use suggestions mentioned in multiple answers to this question - provide custom empty state cell.

I'll try to walk you through all the steps programmatically in more detail and hopefully, this will be helpful. Here's the result we can expect:

enter image description here

For simplicity's sake, we will work with 2 data sets (2 sections), those will be static.

I will also assume that you have the rest of your tableView logic working properly with datasets, tabvleView cells, and sections.

Swift 5, let's do it:

1. Create a custom empty state UITableViewCell class:

class EmptyTableViewCell: UITableViewCell {

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupView()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    let label: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Empty State Message"
        label.font = .systemFont(ofSize: 16)
        label.textColor = .gray
        label.textAlignment = .left
        label.numberOfLines = 1
        return label
    }()

    private func setupView(){
        contentView.addSubviews(label)
        let layoutGuide = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            label.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor),
            label.topAnchor.constraint(equalTo: layoutGuide.topAnchor),
            label.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor),
            label.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor),
            label.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
}

2. Add the following to your UITableViewController class to register your empty cell:

class TableViewController: UITableViewController {
	...
	let emptyCellReuseIdentifier = "emptyCellReuseIdentifier"
	...
	override func viewDidLoad(){
		...
		tableView.register(EmptyTableViewCell.self, forCellReuseIdentifier: emptyCellReuseIdentifier)
		...
	}
}

3. Now let's highlight some assumptions mentioned above:

class TableViewController: UITableViewController {
	// 2 Data Sets
	var firstDataSet: [String] = []
	var secondDataSet: [String] = []

	// Sections array
	let sections: [SectionHeader] = [
		.init(id: 0, title: "First Section"),
        .init(id: 1, title: "Second Section")
	]
	...
	// MARK: - Table view data source
	override func numberOfSections(in tableView: UITableView) -> Int {
        sections.count
    }
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return sections[section].title
    }
        ...
    }

struct SectionHeader {
    let id: Int
    let title: String
}

4. Now let's add some custom logic to our Data Source to handle Empty Rows in our sections. Here we are returning 1 row if a data set is empty:

class TableViewController: UITableViewController {
	...
	// MARK: - Table view data source
	...
	override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch section{
        case 0:
        	let numberOfRows = firstDataSet.isEmpty ? 1 : firstDataSet.count
            return numberOfRows          
        case 1:
            let numberOfRows = secondDataSet.isEmpty ? 1 : secondDataSet.count
            return numberOfRows   
        default:
            return 0
        }
    }
	...
}

5. Lastly, the most important "cellForRowAt indexPath":

class TableViewController: UITableViewController {
	...
	// MARK: - Table view data source
	...
	override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        // Handle Empty Rows State
        switch indexPath.section {
        case 0:
            if firstDataSet.isEmpty {
                if let cell = tableView.dequeueReusableCell(withIdentifier: emptyCellReuseIdentifier) as? EmptyTableViewCell {
                    cell.label.text = "First Data Set Is Empty"
                    return cell
                }
            }
        case 1:
            if secondDataSet.isEmpty {
                if let cell = tableView.dequeueReusableCell(withIdentifier: emptyCellReuseIdentifier) as? EmptyTableViewCell {
                    cell.label.text = "Second Data Set Is Empty"
                    return cell
                }
            }
        default:
            break
        }
        
        // Handle Existing Data Sets
        if let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath) as? TableViewCell {
            switch indexPath.section {
            case 0:
                ...
            case 1:
                ...
            default:
                break
            }
            return cell
        }

        return UITableViewCell()
    }
	...
}

Solution 13 - Iphone

First, the problems with other popular approaches.

BackgroundView

Background view doesn't center nicely if you were to use the simple case of setting it to be a UILabel.

Cells, headers, or footers to display the message

This interferes with your functional code and introduces weird edge cases. If you want to perfectly center your message, that adds another level of complexity.

Rolling your own table view controller

You lose built-in functionality, such as refreshControl, and re-invent the wheel. Stick to UITableViewController for the best maintainable results.

Adding UITableViewController as a child view controller

I have a feeling you'll end up with contentInset issues in iOS 7+ - plus why complicate things?

My solution

The best solution I've come up with (and, granted, this isn't ideal) is to make a special view that can sit on top of a scroll view and act accordingly. This obviously gets complicated in iOS 7 with contentInset madness, but it's doable.

Things you have to watch out for:

  • table separators get brought to front at some point during reloadData - you need to guard against that
  • contentInset/contentOffset - observe those keys on your special view
  • keyboard - if you don't want the keyboard to get in the way, that's another calculation
  • autolayout - you can't depend on frame changes to position your view

Once you have this figured out once in one UIView subclass, you can use it for everything - loading spinners, disabling views, showing error messages, etc.

Solution 14 - Iphone

You can add this to your Base class.

var messageLabel = UILabel()

func showNoDataMessage(msg: String) {
    let rect = CGRect(origin: CGPoint(x: 0, y :self.view.center.y), size: CGSize(width: self.view.bounds.width - 16, height: 50.0))
    messageLabel = UILabel(frame: rect)
    messageLabel.center = self.view.center
    messageLabel.text = msg
    messageLabel.numberOfLines = 0
    messageLabel.textColor = Colors.grayText
    messageLabel.textAlignment = .center;
    messageLabel.font = UIFont(name: "Lato-Regular", size: 17)
    self.view.addSubview(messageLabel)
    self.view.bringSubviewToFront(messageLabel)
}

Show it like this in the class on getting the data from api.

func populateData(dataSource : [PRNJobDataSource]){
    self.dataSource = dataSource
    self.tblView.reloadData()
    if self.dataSource.count == 0 {self.showNoDataMessage(msg: "No data found.")}
}

Hide it like this.

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if self.dataSource.count > 0 {self.hideNoDataMessage()}
    return dataSource.count
}

func hideNoDataMessage(){
    messageLabel.removeFromSuperview()
}

 

Solution 15 - Iphone

Select your tableviewController Scene in storyboard

enter image description here

Drag and drop UIView Add label with your message (eg: No Data)

enter image description here

create outlet of UIView (say for eg yournoDataView) on your TableViewController.

and in viewDidLoad

self.tableView.backgroundView = yourNoDataView

Solution 16 - Iphone

Show Message for empty list, Wether its UITableView or UICollectionView.

extension UIScrollView {
    func showEmptyListMessage(_ message:String) {
        let rect = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: self.bounds.size.width, height: self.bounds.size.height))
        let messageLabel = UILabel(frame: rect)
        messageLabel.text = message
        messageLabel.textColor = .black
        messageLabel.numberOfLines = 0
        messageLabel.textAlignment = .center
        messageLabel.font = UIFont.systemFont(ofSize: 15)
        messageLabel.sizeToFit()
        
        if let `self` = self as? UITableView {
            self.backgroundView = messageLabel
            self.separatorStyle = .none
        } else if let `self` = self as? UICollectionView {
            self.backgroundView = messageLabel
        }
    }
}

Usages:

if cellsViewModels.count == 0 {
    self.tableView.showEmptyListMessage("No Product In List!")
}

OR:

if cellsViewModels.count == 0 {
    self.collectionView?.showEmptyListMessage("No Product In List!")
}

Remember: Don't forget to remove the message label in case data will come after refresh.

Solution 17 - Iphone

The easiest and quickest way to do this is to drag a label on to side panel under tableView. Create a outlet for the label and the tableView and add a if statement to hide and show the label and table as needed. Alternatively you can add tableView.tableFooterView = UIView(frame: CGRect.zero) this to you viewDidLoad() to give an empty table the perception that it is hidden if the table and background view have the same colour.

Solution 18 - Iphone

Using Swift 4.2

  func numberOfSections(in tableView: UITableView) -> Int
{
    var numOfSections: Int = 0
    if self.medArray.count > 0
    {
        tableView.separatorStyle = .singleLine
        numOfSections            = 1
        tableView.backgroundView = nil
    }
    else
    {
        let noDataLabel: UILabel  = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: tableView.bounds.size.height))
        noDataLabel.text          = "No  Medicine available.Press + to add New Pills "
        noDataLabel.textColor     = UIColor.black
        noDataLabel.textAlignment = .center
        tableView.backgroundView  = noDataLabel
        tableView.separatorStyle  = .none
    }
    return numOfSections
}

Solution 19 - Iphone

**Swift version but better and simpler form . 3.0

I hope it server your purpose......

In your UITableViewController .

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if searchController.isActive && searchController.searchBar.text != "" {
        if filteredContacts.count > 0 {
            self.tableView.backgroundView = .none;
            return filteredContacts.count
        } else {
            Helper.EmptyMessage(message: ConstantMap.NO_CONTACT_FOUND, viewController: self)
            return 0
        }
    } else {
        if contacts.count > 0 {
            self.tableView.backgroundView = .none;
            return contacts.count
        } else {
            Helper.EmptyMessage(message: ConstantMap.NO_CONTACT_FOUND, viewController: self)
            return 0
        }
    }
}

Helper Class with function :

 /* Description: This function generate alert dialog for empty message by passing message and
           associated viewcontroller for that function
           - Parameters:
            - message: message that require for  empty alert message
            - viewController: selected viewcontroller at that time
         */
        static func EmptyMessage(message:String, viewController:UITableViewController) {
            let messageLabel = UILabel(frame: CGRect(x: 0, y: 0, width: viewController.view.bounds.size.width, height: viewController.view.bounds.size.height))
            messageLabel.text = message
            let bubbleColor = UIColor(red: CGFloat(57)/255, green: CGFloat(81)/255, blue: CGFloat(104)/255, alpha :1)
            
            messageLabel.textColor = bubbleColor
            messageLabel.numberOfLines = 0;
            messageLabel.textAlignment = .center;
            messageLabel.font = UIFont(name: "TrebuchetMS", size: 18)
            messageLabel.sizeToFit()
            
            viewController.tableView.backgroundView = messageLabel;
            viewController.tableView.separatorStyle = .none;
        }

Solution 20 - Iphone

Probably not the greatest solution, but I did this by just putting a label at the bottom of my table and if the rows = 0 then I assign it some text. Pretty easy, and achieves what you are trying to do with a few lines of code.

I have two sections in my table (jobs and schools)

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
    if (jobs.count == 0 && schools.count == 0) {
        emptyLbl.text = "No jobs or schools"
    } else {
        emptyLbl.text = ""
    }

Solution 21 - Iphone

I made a few changes so that we don't need to check on the count manually, also i added constraints for the label so that nothing goes wrong no matter how large is the message as shown below:

extension UITableView {
    
    fileprivate func configureLabelLayout(_ messageLabel: UILabel) {
        messageLabel.translatesAutoresizingMaskIntoConstraints = false
        let labelTop: CGFloat = CGFloat(UIDevice.current.userInterfaceIdiom == .pad ? 25:15)
        messageLabel.topAnchor.constraint(equalTo: backgroundView?.topAnchor ?? NSLayoutAnchor(), constant: labelTop).isActive = true
        messageLabel.widthAnchor.constraint(equalTo: backgroundView?.widthAnchor ?? NSLayoutAnchor(), constant: -20).isActive = true
        messageLabel.centerXAnchor.constraint(equalTo: backgroundView?.centerXAnchor ?? NSLayoutAnchor(), constant: 0).isActive = true
    }
    
    fileprivate func configureLabel(_ message: String) {
        let messageLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height))
        messageLabel.textColor = .black
        messageLabel.numberOfLines = 0
        messageLabel.textAlignment = .center
        let fontSize = CGFloat(UIDevice.current.userInterfaceIdiom == .pad ? 25:15)
        let font: UIFont = UIFont(name: "MyriadPro-Regular", size: fontSize) ?? UIFont()
        messageLabel.font = font
        messageLabel.text = message
        self.backgroundView = UIView()
        self.backgroundView?.addSubview(messageLabel)
        configureLabelLayout(messageLabel)
        self.separatorStyle = .none
    }
    
    func setEmptyMessage(_ message: String, _ isEmpty: Bool) {
        if isEmpty { // instead of making the check in every TableView DataSource in the project
            configureLabel(message)
        }
        else {
            restore()
        }
        
    }
    
    func restore() {
        self.backgroundView = nil
        self.separatorStyle = .singleLine
    }
}

Usage

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let message: String = "The list is empty."
        ticketsTableView.setEmptyMessage(message, tickets.isEmpty)
        return self.tickets.count
    }

Solution 22 - Iphone

Or alternatively you can use a bit customizable light-weight library

SwiftEmptyState

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncateofView Question on Stackoverflow
Solution 1 - IphoneRay FixView Answer on Stackoverflow
Solution 2 - IphoneFrankieView Answer on Stackoverflow
Solution 3 - IphoneJohnstonView Answer on Stackoverflow
Solution 4 - IphoneHapekiView Answer on Stackoverflow
Solution 5 - IphoneSergey KalinichenkoView Answer on Stackoverflow
Solution 6 - IphoneCarlos BView Answer on Stackoverflow
Solution 7 - IphonepasqlView Answer on Stackoverflow
Solution 8 - IphoneMeseeryView Answer on Stackoverflow
Solution 9 - IphonemangerlahnView Answer on Stackoverflow
Solution 10 - IphoneAmitPView Answer on Stackoverflow
Solution 11 - IphoneRajesh MauryaView Answer on Stackoverflow
Solution 12 - IphoneReposeView Answer on Stackoverflow
Solution 13 - IphonextravarView Answer on Stackoverflow
Solution 14 - IphoneMudassir AsgharView Answer on Stackoverflow
Solution 15 - Iphonegaurav bhardwajView Answer on Stackoverflow
Solution 16 - IphoneGurjit SinghView Answer on Stackoverflow
Solution 17 - IphoneyoamodView Answer on Stackoverflow
Solution 18 - IphoneM MurtezaView Answer on Stackoverflow
Solution 19 - IphoneRavindra ShekhawatView Answer on Stackoverflow
Solution 20 - IphoneZachView Answer on Stackoverflow
Solution 21 - IphoneMostafa SultanView Answer on Stackoverflow
Solution 22 - IphoneEnes KaraosmanView Answer on Stackoverflow