SRMR
SRMR

Reputation: 3154

If no Table View results, display "No Results" on screen

I have a tableview, where sometimes there might not be any results to list, so I would like to put something up that says "no results" if there are no results (either a label or one table view cell?).

Is there an easiest way to do this?

I would try a label behind the tableview then hide one of the two based on the results, but since I'm working with a TableViewController and not a normal ViewController I'm not sure how smart or doable that is.

I'm also using Parse and subclassing as a PFQueryTableViewController:

@interface TableViewController : PFQueryTableViewController

I can provide any additional details needed, just let me know!

TableViewController Scene in Storyboard:

enter image description here

EDIT: Per Midhun MP, here's the code I'm using

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    NSInteger numOfSections = 0;
    if ([self.stringArray count] > 0)
    {
        self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
        numOfSections                 = 1;
        //yourTableView.backgroundView   = nil;
        self.tableView.backgroundView = nil;
    }
    else
    {
        UILabel *noDataLabel         = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.tableView.bounds.size.width, self.tableView.bounds.size.height)];
        noDataLabel.text             = @"No data available";
        noDataLabel.textColor        = [UIColor blackColor];
        noDataLabel.textAlignment    = NSTextAlignmentCenter;
        //yourTableView.backgroundView = noDataLabel;
        //yourTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        self.tableView.backgroundView = noDataLabel;
        self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    }

    return numOfSections;
}

And here's the View I'm getting, it still has separator lines. I get the feeling that this is some small change, but I'm not sure why separator lines are showing up?

enter image description here

Upvotes: 120

Views: 75028

Answers (15)

Jan L
Jan L

Reputation: 253

Many answers here require you to add ui code to numberOfSections TableView extension, which you should really avoid doing. Instead, here is a Swift 5 solution that works just as well.

  1. Add a Class variable let noDataLabel = UILabel() and add it as a subview to your View:

     noDataLabel.text = "no data string"
     noDataLabel.textColor = .white
     noDataLabel.numberOfLines = 0
     noDataLabel.textAlignment = .center
     noDataLabel.isHidden = true
     noDataLabel.layer.zPosition = 2
     noDataLabel.translatesAutoresizingMaskIntoConstraints = false
     view.addSubview(noDataLabel)
    

Just to be sure, you can set its zPosition to be one above your TableView. Your label should be set to isHidden = true. Don't forget to set translateAutoresizingMaskIntoConstraints to false and add the necessary constraints:

NSLayoutConstraint.activate([
    //Fill Screen
    tableView.widthAnchor.constraint(equalTo: view.widthAnchor),
    tableView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    tableView.heightAnchor.constraint(equalTo: view.heightAnchor),
    tableView.topAnchor.constraint(equalTo: view.topAnchor),
    //Center
    noDataLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    noDataLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    noDataLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
])

That's pretty much it! Now, inside of your update() function (called when you refresh your tableView for example, or whenever you try to load data), add the following lines of code:

        self.tableView.reloadData()
        if dataArray.isEmpty {
            noDataLabel.isHidden = false
        }else{
            noDataLabel.isHidden = true
        }
        refreshControl.endRefreshing()

Be sure to add your if else just below your tableView reloadData function.

Upvotes: 0

ShrikantWalekar
ShrikantWalekar

Reputation: 145

Here is the solution that worked for me.

  1. Add the following code to a new file.

  2. Change your table class to the custom class "MyTableView" from storyboard or .xib

(this will work for the first section only. If you want to customize more, do changes in the MyTableView reloadData() function accordingly for other sections)

public class MyTableView: UITableView {

    override public func reloadData() {
        super.reloadData()

        if self.numberOfRows(inSection: 0) == 0 {
            if self.viewWithTag(1111) == nil {
                let noDataLabel = UILabel()
                noDataLabel.textAlignment = .center
                noDataLabel.text = "No Data Available"
                noDataLabel.tag = 1111
                noDataLabel.center = self.center
                self.backgroundView = noDataLabel
            }

        } else {
            if self.viewWithTag(1111) != nil {
                self.backgroundView = nil
            }
        }
    }
}

Upvotes: 2

ShrikantWalekar
ShrikantWalekar

Reputation: 145

Add this code in one file and change your collection type to CustomCollectionView

import Foundation

class CustomCollectionView: UICollectionView {

  var emptyModel = EmptyMessageModel()
  var emptyView: EmptyMessageView?
  var showEmptyView: Bool = true

  override func reloadData() {
    super.reloadData()

    emptyView?.removeFromSuperview()
    self.backgroundView = nil

    if !showEmptyView {
      return
    }

    if numberOfSections < 1 {
      let rect = CGRect(x: 0,
                        y: 0,
                        width: self.bounds.size.width,
                        height: self.bounds.size.height)

      emptyView = EmptyMessageView()
      emptyView?.frame = rect
      if let emptyView = emptyView {
        //                self.addSubview(emptyView)
        self.backgroundView = emptyView
      }
      emptyView?.setView(with: emptyModel)

    } else {
      emptyView?.removeFromSuperview()
      self.backgroundView = nil
    }
  }
}

class EmptyMessageView: UIView {

  @IBOutlet weak var messageLabel: UILabel!
  @IBOutlet weak var imageView: UIImageView!

  var view: UIView!

  override init(frame: CGRect) {
    super.init(frame: frame)
    xibSetup()
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    xibSetup()
  }

  func xibSetup() {
    view = loadViewFromNib()

    view.frame = bounds

    view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    addSubview(view)
  }

  func loadViewFromNib() -> UIView {

    let bundle = Bundle(for: type(of: self))
    let nib = UINib(nibName: "EmptyMessageView", bundle: bundle)
    let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView

    return view
  }

  func setView(with model: EmptyMessageModel) {
    messageLabel.text = model.message ?? ""
    imageView.image = model.image ?? #imageLiteral(resourceName: "no_notification")
  }
}

///////////
class EmptyMessageModel {
  var message: String?
  var image: UIImage?

  init(message: String = "No data available", image: UIImage = #imageLiteral(resourceName: "no_notification")) {
    self.message = message
    self.image = image
  }
}

Upvotes: 1

Midhun MP
Midhun MP

Reputation: 107231

You can easily achieve that by using backgroundView property of UITableView.

Objective C:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    NSInteger numOfSections = 0;
    if (youHaveData)
    {
        yourTableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
        numOfSections                = 1;
        yourTableView.backgroundView = nil;
    }
    else
    {   
        UILabel *noDataLabel         = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, yourTableView.bounds.size.width, yourTableView.bounds.size.height)];
        noDataLabel.text             = @"No data available";
        noDataLabel.textColor        = [UIColor blackColor];
        noDataLabel.textAlignment    = NSTextAlignmentCenter;
        yourTableView.backgroundView = noDataLabel;
        yourTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    }

    return numOfSections;
}

Swift:

func numberOfSections(in tableView: UITableView) -> Int
{
    var numOfSections: Int = 0
    if youHaveData
    {
        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 data available"
        noDataLabel.textColor     = UIColor.black
        noDataLabel.textAlignment = .center
        tableView.backgroundView  = noDataLabel
        tableView.separatorStyle  = .none
    }
    return numOfSections
}

Reference UITableView Class Reference

backgroundView Property

The background view of the table view.

Declaration

Swift

var backgroundView: UIView?

Objective-C

@property(nonatomic, readwrite, retain) UIView *backgroundView

Discussion

A table view’s background view is automatically resized to match the size of the table view. This view is placed as a subview of the table view behind all cells, header views, and footer views.

You must set this property to nil to set the background color of the table view.

Upvotes: 218

F. Morales
F. Morales

Reputation: 477

If you want to do this without any code, try this!

Click on your tableView.

tableView Screenshot

Change the style from "plain" to "grouped".

Table View properties screenshot-2

Now when you use ....

tableView.backgroundView = INSERT YOUR LABEL OR VIEW

It will not show the separators!

Upvotes: 1

Chandan kumar
Chandan kumar

Reputation: 1084

Use this code in Your numberOfSectionsInTableView method:-

if ([array count]==0
{

    UILabel *fromLabel = [[UILabel alloc]initWithFrame:CGRectMake(50, self.view.frame.size.height/2, 300, 60)];                                                                                        
    fromLabel.text =@"No Result";
    fromLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
    fromLabel.backgroundColor = [UIColor clearColor];
    fromLabel.textColor = [UIColor lightGrayColor];
    fromLabel.textAlignment = NSTextAlignmentLeft;
    [fromLabel setFont:[UIFont fontWithName:Embrima size:30.0f]];
    [self.view addSubview:fromLabel];
    [self.tblView setHidden:YES];
}

Upvotes: 3

frmi
frmi

Reputation: 517

If you don't use the tableview footer and do not want the tableview to fill up the screen with empty default table cells i would suggest that you set your tableview footer to an empty UIView. I do not know the correct syntax for doing this in obj-c or Swift, but in Xamarin.iOS i would do it like this:

public class ViewController : UIViewController
{
    UITableView _table;

    public ViewController (IntPtr handle) : base (handle)
    {
    }

    public override void ViewWillAppear(bool animated) {
        // Initialize table

        _table.TableFooterView = new UIView();
    }
}

Above code will result in a tableview without the empty cells

Upvotes: 2

Paul Bonneville
Paul Bonneville

Reputation: 1945

For Xcode 8.3.2 - Swift 3.1

Here is a not-so-well-known but incredibly easy way to achieve adding a "No Items" view to an empty table view that goes back to Xcode 7. I'll leave it to you control that logic that adds/removes the view to the table's background view, but here is the flow for and Xcode (8.3.2) storyboard:

  1. Select the scene in the Storyboard that has your table view.
  2. Drag an empty UIView to the "Scene Dock" of that scene

enter image description here

  1. Add a UILabel and any constraints to the new view and then create an IBOutlet for that view

enter image description here

  1. Assign that view to the tableView.backgroundView

enter image description here

  1. Behold the magic!

enter image description here

Ultimately this works anytime you want to add a simple view to your view controller that you don't necessarily want to be displayed immediately, but that you also don't want to hand code.

Upvotes: 113

Ravindra Shekhawat
Ravindra Shekhawat

Reputation: 4363

Swift3.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;
        }

Upvotes: 6

Teodor Ciuraru
Teodor Ciuraru

Reputation: 3477

Swift 3 (updated):

override func numberOfSections(in tableView: UITableView) -> Int {
    if myArray.count > 0 {
        self.tableView.backgroundView = nil
        self.tableView.separatorStyle = .singleLine
        return 1
    }

    let rect = CGRect(x: 0,
                      y: 0,
                      width: self.tableView.bounds.size.width,
                      height: self.tableView.bounds.size.height)
    let noDataLabel: UILabel = UILabel(frame: rect)

    noDataLabel.text = "Custom message."
    noDataLabel.textColor = UIColor.white
    noDataLabel.textAlignment = NSTextAlignment.center
    self.tableView.backgroundView = noDataLabel
    self.tableView.separatorStyle = .none

    return 0
}

Upvotes: 6

Ahmed Safadi
Ahmed Safadi

Reputation: 4600

SWIFT 3

        let noDataLabel: UILabel     = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: tableView.bounds.size.height))
        noDataLabel.text             = "No data available"
        noDataLabel.textColor        = UIColor.white
        noDataLabel.font             = UIFont(name: "Open Sans", size: 15)
        noDataLabel.textAlignment    = .center
        tableView.backgroundView = noDataLabel
        tableView.separatorStyle = .none

Upvotes: 2

Mudith Chathuranga Silva
Mudith Chathuranga Silva

Reputation: 7444

Swift Version of above code :-

func numberOfSectionsInTableView(tableView: UITableView) -> Int {

    var numOfSection: NSInteger = 0

    if CCompanyLogoImage.count > 0 {

        self.tableView.backgroundView = nil
        numOfSection = 1


    } else {

        var noDataLabel: UILabel = UILabel(frame: CGRectMake(0, 0, self.tableView.bounds.size.width, self.tableView.bounds.size.height))
        noDataLabel.text = "No Data Available"
        noDataLabel.textColor = UIColor(red: 22.0/255.0, green: 106.0/255.0, blue: 176.0/255.0, alpha: 1.0)
        noDataLabel.textAlignment = NSTextAlignment.Center
        self.tableView.backgroundView = noDataLabel

    }
    return numOfSection
}

But If you are loading Information From a JSON , you need to check whether the JSON is empty or not , therefor if you put code like this it initially shows "No data" Message then disappear. Because after the table reload data the message hide. So You can put this code where load JSON data to an array. SO :-

func numberOfSectionsInTableView(tableView: UITableView) -> Int {

    return 1
}

func extract_json(data:NSData) {


    var error: NSError?

    let jsonData: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers , error: &error)
    if (error == nil) {
        if let jobs_list = jsonData as? NSArray
        {
            if jobs_list.count == 0 {

                var noDataLabel: UILabel = UILabel(frame: CGRectMake(0, 0, self.tableView.bounds.size.width, self.tableView.bounds.size.height))
                noDataLabel.text = "No Jobs Available"
                noDataLabel.textColor = UIColor(red: 22.0/255.0, green: 106.0/255.0, blue: 176.0/255.0, alpha: 1.0)
                noDataLabel.textAlignment = NSTextAlignment.Center
                self.tableView.backgroundView = noDataLabel

            }

            for (var i = 0; i < jobs_list.count ; i++ )
            {
                if let jobs_obj = jobs_list[i] as? NSDictionary
                {
                    if let vacancy_title = jobs_obj["VacancyTitle"] as? String
                    {
                        CJobTitle.append(vacancy_title)

                        if let vacancy_job_type = jobs_obj["VacancyJobType"] as? String
                        {
                            CJobType.append(vacancy_job_type)

                            if let company_name = jobs_obj["EmployerCompanyName"] as? String
                            {
                                CCompany.append(company_name)

                                    if let company_logo_url = jobs_obj["EmployerCompanyLogo"] as? String
                                    {
                                        //CCompanyLogo.append("http://google.com" + company_logo_url)

                                        let url = NSURL(string: "http://google.com" + company_logo_url )
                                        let data = NSData(contentsOfURL:url!)
                                        if data != nil {
                                            CCompanyLogoImage.append(UIImage(data: data!)!)
                                        }

                                        if let vacancy_id = jobs_obj["VacancyID"] as? String
                                        {
                                            CVacancyId.append(vacancy_id)

                                        }

                                    }

                            }

                        }
                    }
                }
            }
        }
    }
    do_table_refresh();


}

func do_table_refresh() {

    dispatch_async(dispatch_get_main_queue(), {
        self.tableView.reloadData()
        return
    })
}

Upvotes: 30

Cihan Tek
Cihan Tek

Reputation: 5409

I think the most elegant way to solve your problem is switching from a UITableViewController to a UIViewController that contains a UITableView. This way you can add whatever UIView you want as subviews of the main view.

I wouldn't recommend using a UITableViewCell to do this you might need to add additional things in the future and things can quicky get ugly.

You can also do something like this, but this isn't the best solution either.

UIWindow* window = [[UIApplication sharedApplication] keyWindow];
[window addSubview: OverlayView];

Upvotes: 3

Sam B
Sam B

Reputation: 27618

You can try this control. Its is pretty neat. DZNEmptyDataSet

Or if I were you all I would do is

  • Check to see if your data array is empty
  • If it is empty then add one object called @"No Data" to it
  • Display that string in cell.textLabel.text

Easy peasy

Upvotes: 7

Siriss
Siriss

Reputation: 3777

I would present a an overlay view that has the look and message you want if the tableview has no results. You could do it in ViewDidAppear, so you have the results before showing/not showing the view.

Upvotes: 2

Related Questions