Handling an empty UITableView. Print friendly message

I have a UITableView, which in some cases is legal to be empty. Therefore, instead of showing the background image of the application, I would prefer to print a friendly message on the screen, for example:

This list is empty.

What is the easiest way to do this?

+95
ios iphone uitableview uiview
Apr 01 '13 at 15:52
source share
19 answers

The backgroundView property of the UITableView is your friend.

In viewDidLoad or anywhere you reloadData , you should determine whether your table exists empty or not, and update the backgroundView property of the UITableView with a UIView containing the UILabel, or simply set it to zero. What is it.

Of course, you can make the UITableView data source double-watch and return the special "list is empty" cell, it amazes me like kludge. Suddenly, the numberOfRowsInSection:(NSInteger)section should calculate the number of rows of other sections that were not asked to make sure that they are also empty. You also need to create a special cell with an empty message. Also, don't forget that you probably need to change the height of your cell to post a blank message. All this can be done, but it looks like a strip is on top of the strip help.

+148
Jul 02 '13 at 22:20
source share

Based on the answers given here, I will provide a brief class that you can use 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 http://www.appcoda.com/pull-to-refresh-uitableview-empty/

+73
Jun 05 '16 at 14:13
source share

Same as Jhonston, but I preferred it as an extension:

 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 } } 

Using:

 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 } 
+44
Jul 18 '17 at 4:16
source share

I recommend the following library: DZNEmptyDataSet

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

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

 import DZNEmptyDataSet 

Then make sure your class matches DNZEmptyDataSetSource and DZNEmptyDataSetDelegate as follows:

 class MyTableViewController: UITableViewController, DZNEmptyDataSetSource, DZNEmptyDataSetDelegate 

Add the following lines of code to your viewDidLoad :

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

Now all you have to do to show 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 are optional; you can also simply show an 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) } 
+14
Jun 05 '16 at 2:30 p.m.
source share

One way to do this is to change the data source to return 1 when the number of rows is zero, and create a special cell (possibly 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 can get complicated if you have several table view controllers that you need to support because someone will eventually forget to insert a null check. The best approach is to create a separate implementation of the UITableViewDataSource implementation that always returns a single row with a custom message (let us call it EmptyTableViewDataSource ). When the data managed by the table view controller changes, the change control code will check if the data is empty. If it is not empty, install a table view controller with its usual data source; otherwise, install it with an instance of EmptyTableViewDataSource that has been configured with the appropriate message.

+12
Apr 01 '13 at 15:57
source share

For this, I use the titleForFooterInSection message. 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; } 
+9
03 Feb '15 at 11:40
source share

I can only recommend dragging and rendering a UITextView inside the TableView after the cells. Make a connection to the ViewController and hide / show it when necessary (for example, whenever the table is reloaded).

enter image description here

+5
Apr 18 '16 at 0:14
source share

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 } } 

as well as for UICollectionView :

 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 general 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 } } 
+5
Dec 08 '17 at 13:52
source share

Using backgroundView is fine, but it doesn't scroll well, like in Mail.app.

I did something similar to what xtravar did.

I added a view outside the tableViewController 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 

I basically added emptyStateView as a subset of the tableView object. Since the delimiters will overlap the view, I set their color to clearColor . To revert to the default separator color, you can simply set it to nil .

+4
Sep 06 '16 at 19:49
source share

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

I put all my empty states in a separate storyboard. Each of them has its own subclass of UIViewController. I am adding content right below their root view. If any action / button is required, now you already have a controller to handle it.
Then itโ€™s just a matter of creating the required view controller from this storyboard, add it as a child view controller and add the container view to the tableView hierarchy (for viewing). Your empty state view will also scroll, which will be fine, and you can implement pull to update.

Check out the chapter โ€œAdding a child view controller to your contentโ€ for help on how to implement it.

Just make sure you set the child view frame to (0, 0, tableView.frame.width, tableView.frame.height) and everything will be centered and aligned correctly.

+4
Jan 12 '17 at 16:10
source share

Firstly, problems with other popular approaches.

Backgroundview

The background view is not centered if you used the simple case of installing UILabel.

Cells, headers, or footers to display a message

This interferes with your functional code and introduces strange edge cases. If you want to completely concentrate your message, this will add another level of complexity.

Moving your own table view controller

You lose built-in features like refreshControl and reinvent the wheel. Stick to the UITableViewController for the best supported results.

Adding a UITableViewController as a Child View Controller

I got the feeling that in the end you will encounter problems with content in iOS 7+ - plus, why complicate it?

My decision

The best solution I came up with (and provided is not perfect) is to make a special look that can sit on top of the scroll and act accordingly. This is clearly complicated in iOS 7 with the frenzy of the contentInset, but it is doable.

Things you should pay attention to:

  • table separators come to the fore at some point during reloadData - you need to protect yourself from this
  • contentInset / contentOffset - respect these keys in your custom view
  • keyboard - if you do not want the keyboard to interfere, this is another calculation
  • autolayout - you cannot depend on frame changes to position your view.

Once you have it parsed once in one subclass of UIView, you can use it for everything: loading clips, disabling views, displaying error messages, etc.

+3
Mar 16 '14 at 23:53
source share

This is the best and easiest 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; 
+2
06 Sep '17 at 18:31 on
source share

Show a message for an empty list, 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 } } } 

Customs:

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

OR:

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

Remember: do not forget to remove the message label if the data appears after the update.

+2
Jun 22 '18 at 8:28
source share

Select the TableviewController scene in the storyboard

enter image description here

Drag UIView Add a shortcut with your message (for example: No data)

enter image description here

create an output from a UIView (say, for example, yournoDataView) on your TableViewController.

and in viewDidLoad

self.tableView.backgroundView = yourNoDataView

+1
Sep 29 '17 at 15:59
source share

A quick version, but a simpler and simpler form. ** 3.0

Hope this is your goal server ......

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; } 
0
Jan 12 '17 at 12:24
source share

This is probably not the best solution, but I did it by simply placing a label at the bottom of my table, and if the row = 0, I assign it some text. Quite easily, what you are trying to do with a few lines of code is achieved.

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 = "" } 
0
Jul 05 '17 at 7:37
source share

The easiest and fastest way to do this is to drag the label onto the sidebar under the tableView. Create an output for the label and tableView and add an if statement to hide and show the label and table as needed. Alternatively, you can add tableView.tableFooterView = UIView (frame: CGRect.zero) to you viewDidLoad () to give the empty table an idea that it is hidden if the table and background view have the same color.

0
Aug 16 '18 at 11:34
source share

Then give problems with labeling (not to mention the translation of the message!). I really like this simple solution - just hide (or show) the cell separators after updating the model:

 - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { self.tableView.separatorStyle = (controller.fetchedObjects.count ? UITableViewCellSeparatorStyleSingleLine : UITableViewCellSeparatorStyleNone); [self.tableView endUpdates]; } 

Also in the storyboard, set the table so that there is no separator for the first appearance.

0
Jan 13 '19 at 18:08
source share

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 } 
0
May 2 '19 at 10:43
source share



All Articles