Jeśli nie ma wyników w widoku tabeli, wyświetl na ekranie „Brak wyników”

114

Mam tableview, gdzie czasami może nie być żadnych wyników do wyświetlenia, więc chciałbym umieścić coś, co mówi „brak wyników”, jeśli nie ma żadnych wyników (etykieta lub jedna komórka widoku tabeli?).

Czy jest na to najłatwiejszy sposób?

Spróbowałbym z labeltyłu, a tableviewnastępnie ukrył jeden z dwóch w oparciu o wyniki, ale ponieważ pracuję z TableViewControllera nie normalnym, ViewControllernie jestem pewien, jak to jest inteligentne lub wykonalne.

Używam również Parsei podklasy jako PFQueryTableViewController:

@interface TableViewController : PFQueryTableViewController

Mogę podać dodatkowe informacje, po prostu daj mi znać!

TableViewController Scena w Storyboard:

wprowadź opis obrazu tutaj

EDYCJA: Per Midhun MP, oto kod, którego używam

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

A oto widok, który otrzymuję, wciąż ma linie separatora. Mam wrażenie, że to mała zmiana, ale nie jestem pewien, dlaczego pojawiają się linie separatora?

wprowadź opis obrazu tutaj

SRMR
źródło
Myślę, że sztuczka polegała na ustawieniu pustej stopki w widoku tabeli, aby pozbyć się linii.
Ben Sinclair
@Andy Co przez to rozumiesz?
SRMR
Nie korzystaj z rozwiązania dodanego do tego pytania. Metody źródła danych widoku tabeli nigdy nie mogą robić więcej niż powinny. numberOfSectionspowinien zwrócić liczbę i to wszystko. To samo dotyczy numberOfRowsInSection. Można je wywoływać wielokrotnie w dowolnym momencie. Nigdy nie aktualizuj widoków, nie aktualizuj danych ani nie rób niczego poza zwracaniem liczby. Logika aktualizowania widoków nigdy nie może znajdować się w tych metodach.
rmaddy

Odpowiedzi:

206

Możesz to łatwo osiągnąć za pomocą backgroundViewwłaściwościUITableView .

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

Szybki:

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
}

Odwołanie do klasy UITableView

backgroundView własność

Widok tła widoku tabeli.

Deklaracja

Szybki

var backgroundView: UIView?

Cel C

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

Dyskusja

Rozmiar widoku tła widoku tabeli jest automatycznie zmieniany w celu dopasowania do rozmiaru widoku tabeli. Ten widok jest umieszczany jako widok podrzędny widoku tabeli za wszystkimi komórkami, widokami nagłówków i widokami stopki.

Aby ustawić kolor tła widoku tabeli, należy ustawić tę właściwość na nil.

Midhun MP
źródło
Dziękuję za odpowiedź! Czy to oznacza, że ​​potrzebuję tableViewgniazdka, żebym mógł to zrobić yourTableView.backgroundView? Używam TableViewControllerwięc teraz nie mam dla niego gniazdka, dlatego się zastanawiam.
SRMR
1
UITableViewControllerma już właściwość o nazwie, tableViewwięc nie musisz jej tworzyć
Cihan Tek,
Rozumiem, używamself.tableView
SRMR
1
Korekta. Po dalszych testach musiałem sprawić, by ta linia, o której wspomniałem, zwracała tę samą wartość. numOfSections = 1. Jeśli ustawię zero, a mój widok tabeli jest pusty, działa, ale kiedy tworzę komórkę, a następnie ją usuwam, zamiast usuwać i wyświetlać komunikat, powoduje awarię z następującym błędem. *** Zamykanie aplikacji z powodu nieprzechwyconego wyjątku „NSInternalInconsistencyException”, przyczyna: „Błąd wewnętrzny UITableView: nie można wygenerować nowej mapy sekcji ze starą liczbą sekcji: 1 i nową liczbą sekcji: 0”. Nie wiem, dlaczego zwracanie tej samej wartości działa, ale działa i działa tak, jak powinno.
ChrisOSX
2
Nie używaj tego rozwiązania. Metody źródła danych widoku tabeli nigdy nie mogą robić więcej niż powinny. numberOfSectionspowinien zwrócić liczbę i to wszystko. To samo dotyczy numberOfRowsInSection. Można je wywoływać wielokrotnie w dowolnym momencie. Nigdy nie aktualizuj widoków, nie aktualizuj danych ani nie rób niczego poza zwracaniem liczby. Logika aktualizowania widoków nigdy nie może znajdować się w tych metodach.
rmaddy
107

W przypadku Xcode 8.3.2 - Swift 3.1

Oto niezbyt dobrze znany, ale niewiarygodnie łatwy sposób na dodanie widoku „Brak elementów” do widoku pustej tabeli, który powraca do Xcode 7. Zostawię ci kontrolę nad logiką, która dodaje / usuwa widok do widoku tła tabeli, ale tutaj jest przepływ dla i scenorysu Xcode (8.3.2):

  1. Wybierz scenę w Storyboard, która zawiera widok tabeli.
  2. Przeciągnij pusty UIView do „Scene Dock” tej sceny

wprowadź opis obrazu tutaj

  1. Dodaj UILabel i wszelkie ograniczenia do nowego widoku, a następnie utwórz IBOutlet dla tego widoku

wprowadź opis obrazu tutaj

  1. Przypisz ten widok do tableView.backgroundView

wprowadź opis obrazu tutaj

  1. Spójrz na magię!

wprowadź opis obrazu tutaj

Ostatecznie działa to zawsze, gdy chcesz dodać prosty widok do kontrolera widoku, który niekoniecznie chcesz, aby był wyświetlany natychmiast, ale także nie chcesz ręcznie kodować.

Paul Bonneville
źródło
1
Ta technika działa w przypadku wszystkiego, do czego potrzebujesz prostego widoku. Użyłem go również do niestandardowych nagłówków sekcji tabeli. Naprawdę, możesz go użyć do wszystkiego, do czego potrzebujesz widoku. Wystarczy odwołać się za pośrednictwem IBOutlet i dodać go jako widok podrzędny w dowolnym miejscu, w którym potrzebujesz niestandardowego widoku.
Paul Bonneville
Jak powiedział @gmogames, po 2 latach używania IOS, po raz pierwszy wiemy o tle tableView! dzięki człowiekowi
Ali Adil
1
Dzięki za to. Aby to opublikować, musiałem użyć tableView.separatorStyle = .none i zmienić rozmiar domyślnego UIView, aby zobaczyć backgroundView. Używam Xcode 9.4 z Swift 4.
Hammad Tariq
Jak umieścić widok niestandardowy?
Nol4635
1
@ Nol4635 W przypadku tła widoku tabeli, wystarczy przypisać widok utworzony jako IBOutlet do tła widoku tabeli lub, jeśli chcesz po prostu dodać widok do dowolnego innego widoku, możesz po prostu dodać go jako widok podrzędny. Będziesz musiał ustawić wartości klatek, ale jest to jak każdy inny widok.
Paul Bonneville
30

Szybka wersja powyższego kodu: -

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
}

Ale jeśli ładujesz informacje z JSON, musisz sprawdzić, czy JSON jest pusty, czy nie, dlatego jeśli umieścisz kod w ten sposób, początkowo wyświetli się komunikat „Brak danych”, a następnie zniknie. Ponieważ po przeładowaniu tabeli dane wiadomość się ukrywa. Możesz więc umieścić ten kod w miejscu ładowania danych JSON do tablicy. WIĘC :-

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
    })
}
Chathuranga Silva
źródło
Działa to bardzo dobrze w przypadku wyświetlania etykiety, gdy w wierszach nie ma danych. Jednak jaki jest najlepszy sposób postępowania, gdy dane przychodzą i self.tableView.reloadData()są wywoływane? Odkryłem, że dodaje moje nowe wiersze, ale noDataLabelnadal jest wyświetlany pod nimi.
tylerSF
1
Jeśli tablica zawiera element, po prostu ustaw self.tableView.backgroundView = nil
Chathuranga Silva
if jobs_list.count == 0 {// dodaj powyższy kod} else {self.tableView.backgroundView = nil}
Chathuranga Silva
idealny. dodanie self.tableView.backgroundView = nilinstrukcji my if przed return jobs_list.count działa dobrze. dzięki
tylerSF
Nie używaj pierwszego rozwiązania w tej odpowiedzi. Metody źródła danych widoku tabeli nigdy nie mogą robić więcej niż powinny. numberOfSectionspowinien zwrócić liczbę i to wszystko. To samo dotyczy numberOfRowsInSection. Można je wywoływać wielokrotnie w dowolnym momencie. Nigdy nie aktualizuj widoków, nie aktualizuj danych ani nie rób niczego poza zwracaniem liczby. Logika aktualizowania widoków nigdy nie może znajdować się w tych metodach.
rmaddy
6

Możesz wypróbować tę kontrolę. Jest całkiem schludny. DZNEmptyDataSet

Albo na twoim miejscu wszystko co bym zrobił

  • Sprawdź, czy tablica danych jest pusta
  • Jeśli jest pusty, dodaj do niego jeden obiekt o nazwie @ „No Data”
  • Wyświetl ten ciąg w cell.textLabel.text

Bułka z masłem

Sam B.
źródło
5

Swift 3 (zaktualizowany):

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
}
Teodor Ciuraru
źródło
Nie używaj tego rozwiązania. Metody źródła danych widoku tabeli nigdy nie mogą robić więcej niż powinny. numberOfSectionspowinien zwrócić liczbę i to wszystko. To samo dotyczy numberOfRowsInSection. Można je wywoływać wielokrotnie w dowolnym momencie. Nigdy nie aktualizuj widoków, nie aktualizuj danych ani nie rób niczego poza zwracaniem liczby. Logika aktualizowania widoków nigdy nie może znajdować się w tych metodach.
rmaddy
Zgadzam się. To rozwiązanie można ulepszyć, ale można je również zastosować.
Teodor Ciuraru
Jak możesz się zgodzić, że nie powinieneś korzystać z tego rozwiązania, ale potem stwierdzić, że można też z niego skorzystać? To jest sprzeczność.
rmaddy
Zgadzam się, że to rozwiązanie można poprawić, ale w obecnym stanie też się sprawdzi.
Teodor Ciuraru
5

Swift3.0

Mam nadzieję, że serwer służy do Twojego celu ...... W Twoim 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
        }
    }
}

Klasa pomocnicza z funkcją:

 /* 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;
        }
Ravindra Shekhawat
źródło
Nie używaj tego rozwiązania. Metody źródła danych widoku tabeli nigdy nie mogą robić więcej niż powinny. numberOfSectionspowinien zwrócić liczbę i to wszystko. To samo dotyczy numberOfRowsInSection. Można je wywoływać wielokrotnie w dowolnym momencie. Nigdy nie aktualizuj widoków, nie aktualizuj danych ani nie rób niczego poza zwracaniem liczby. Logika aktualizowania widoków nigdy nie może znajdować się w tych metodach.
rmaddy
3

Myślę, że najbardziej eleganckim sposobem rozwiązania twojego problemu jest przejście z a UITableViewControllerdo UIViewControllerzawierającego UITableView. W ten sposób możesz dodać cokolwiekUIView chcesz jako podglądy głównego widoku.

Nie polecałbym używania UITableViewCell, aby to zrobić, może być konieczne dodanie dodatkowych rzeczy w przyszłości, a rzeczy mogą szybko stać się brzydkie.

Możesz też zrobić coś takiego, ale to też nie jest najlepsze rozwiązanie.

UIWindow* window = [[UIApplication sharedApplication] keyWindow];
[window addSubview: OverlayView];
Cihan Tek
źródło
Dzięki za kolor, wokół którego jest najbardziej elegancki sposób realizacji, zawsze dobrze o tym pomyśleć. Mogę spróbować zmienić mój scenorys, aby użyć pliku UIViewControllerzawierającego UITableView.
SRMR
1
To najbardziej przyszłościowy sposób. Nigdy nie używałem UITableViewControllersiebie, ponieważ używanie go nie jest tak naprawdę łatwiejsze niż robienie tego w inny sposób, ale ogranicza to twoją zdolność do zmiany rzeczy w przyszłości.
Cihan Tek
Nie jest to przyszłościowe, jeśli masz półprzezroczyste paski. Gorąco odradzam. Przynajmniej utwórz UIViewController, który zawiera UITableViewController i odpowiednio dostosowuje rzeczy.
xtravar
3

Użyj tego kodu w swojej numberOfSectionsInTableViewmetodzie: -

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];
}
Chandan
źródło
1
Nie używaj tego rozwiązania. Metody źródła danych widoku tabeli nigdy nie mogą robić więcej niż powinny. numberOfSectionspowinien zwrócić liczbę i to wszystko. To samo dotyczy numberOfRowsInSection. Można je wywoływać wielokrotnie w dowolnym momencie. Nigdy nie aktualizuj widoków, nie aktualizuj danych ani nie rób niczego poza zwracaniem liczby. Logika aktualizowania widoków nigdy nie może znajdować się w tych metodach.
rmaddy
2

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
Ahmed Safadi
źródło
2

Jeśli nie używasz stopki widoku tabeli i nie chcesz, aby widok tabeli wypełniał ekran pustymi domyślnymi komórkami tabeli, sugerowałbym ustawienie stopki widoku tabeli na puste UIView. Nie znam poprawnej składni do zrobienia tego w obj-c lub Swift, ale w Xamarin.iOS zrobiłbym to tak:

public class ViewController : UIViewController
{
    UITableView _table;

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

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

        _table.TableFooterView = new UIView();
    }
}

Powyższy kod spowoduje wyświetlenie tabeli bez pustych komórek

frmi
źródło
2

Oto rozwiązanie, które zadziałało dla mnie.

  1. Dodaj następujący kod do nowego pliku.

  2. Zmień klasę tabeli na klasę niestandardową „MyTableView” z serii ujęć lub .xib

(zadziała to tylko dla pierwszej sekcji. Jeśli chcesz dostosować więcej, wprowadź zmiany w funkcji MyTableView reloadData () odpowiednio dla innych sekcji)

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
            }
        }
    }
}
ShrikantWalekar
źródło
To jest dobre podejście. Dodałem pewne ulepszenie w następujący sposób, aby było lepiej zaokrąglone. 1. Styl separatora pamięci podręcznej widoku tabeli i przywróć go po przejściu stanu pusty / niepusty, aby ta podklasa miała zastosowanie w przypadkach użycia, w których wymagane są różne style separatorów. 2. Dodaj @IBInspectable var emptyStateText: String?i zaznacz tę klasę, ponieważ @IBDesignablewtedy będziesz mógł zmieniać różne puste teksty stanu dla różnych scen w scenorysie lub xibs. Cieszyć się! :)
Egist Li
1

Przedstawiłbym widok nakładki, który ma wygląd i wiadomość, którą chcesz, jeśli widok tabeli nie daje wyników. Możesz to zrobić w ViewDidAppear, więc masz wyniki przed pokazaniem / niewidocznością widoku.

Siriss
źródło
1

Jeśli chcesz to zrobić bez żadnego kodu, spróbuj tego!

Kliknij swój tableView.

tableView Screenshot

Zmień styl z „zwykły” na „zgrupowany”.

Właściwości widoku tabeli screenshot-2

Teraz, kiedy używasz ....

tableView.backgroundView = WSTAW SWOJĄ ETYKIETĘ LUB WIDOK

Nie pokaże separatorów!

F. Morales
źródło
1

Dodaj ten kod w jednym pliku i zmień typ kolekcji na 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
  }
}
ShrikantWalekar
źródło