Обработка пустого UITableView. Распечатать дружественное сообщение

У меня есть UITableView, что в некоторых случаях законно быть пустым. Поэтому вместо того, чтобы показывать фоновое изображение приложения, я бы предпочел напечатать на экране дружественное сообщение, например:

Этот список пуст

Каков самый простой способ сделать это?

Ответ 1

Свойство backgroundView UITableView - ваш друг.

В viewDidLoad или в любом месте, где вы reloadData, вам следует определить, существует ли ваша таблица пустым или нет, и обновить свойство backgroundView UITableView с UIView, содержащим UILabel, или просто установить его на нуль. Что это.

Конечно, можно сделать источник данных UITableView двойным дежурством и вернуть специальную ячейку "список пуст", это поражает меня как kludge. Внезапно numberOfRowsInSection:(NSInteger)section должен вычислить количество строк других разделов, о которых не спрашивали, чтобы убедиться, что они тоже пустые. Вам также необходимо создать специальную ячейку с пустым сообщением. Также не забывайте, что вам нужно, вероятно, изменить высоту своей ячейки, чтобы разместить пустое сообщение. Это все можно сделать, но похоже, что полоса на верхней части полосовой помощи.

Ответ 2

Основываясь на ответах, приведенных здесь, я приведу краткий класс, который вы можете использовать в своем 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;
    }
}

В вашем UITableViewController вы можете вызвать это в 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

С небольшой помощью http://www.appcoda.com/pull-to-refresh-uitableview-empty/

Ответ 3

То же, что и Jhonston, но я предпочел его как расширение:

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

Использование:

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
}

Ответ 4

Я рекомендую следующую библиотеку: DZNEmptyDataSet

Самый простой способ добавить его в ваш проект - это использовать его с Cocaopods, например так: pod 'DZNEmptyDataSet'

В вашем TableViewController добавьте следующий оператор импорта (Swift):

import DZNEmptyDataSet

Затем убедитесь, что ваш класс соответствует DNZEmptyDataSetSource и DZNEmptyDataSetDelegate следующим образом:

class MyTableViewController: UITableViewController, DZNEmptyDataSetSource, DZNEmptyDataSetDelegate

В ваш viewDidLoad добавьте следующие строки кода:

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

Теперь все, что вам нужно сделать, чтобы показать emptystate:

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

Эти методы не являются обязательными, также можно просто показать пустое состояние без кнопки и т.д.

Для 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)
}

Ответ 5

Одним из способов сделать это будет изменение источника данных для возврата 1, когда число строк равно нулю, и создать специальную ячейку (возможно, с другим идентификатором ячейки) в методе tableView:cellForRowAtIndexPath:.

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

Это может усложниться, если у вас есть несколько контроллеров табличных представлений, которые вам нужно поддерживать, потому что кто-то в конце концов забудет вставить нулевую проверку. Лучшим подходом является создание отдельной реализации реализации UITableViewDataSource, которая всегда возвращает одну строку с настраиваемым сообщением (позвольте называть его EmptyTableViewDataSource). Когда данные, управляемые контроллером табличного представления, меняются, код, управляющий изменением, будет проверять, являются ли данные пустыми. Если он не пуст, установите контроллер табличного представления с его обычным источником данных; в противном случае установите его с экземпляром EmptyTableViewDataSource, который был настроен с соответствующим сообщением.

Ответ 6

Для этого я использую сообщение titleForFooterInSection. Я не знаю, является ли это субоптимальным или нет, но он работает.

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

Ответ 7

Я могу только рекомендовать перетащить и отобразить UITextView внутри TableView после ячеек. Сделайте соединение с ViewController и скройте/отобразите его, когда это необходимо (например, всякий раз, когда перезагрузка таблицы).

введите описание изображения здесь

Ответ 8

Итак, для более безопасного решения:

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

а также для 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
  }
}

Более общее решение:

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

Ответ 9

Использование backgroundView в порядке, но оно не прокручивается хорошо, как в Mail.app.

Я сделал что-то похожее на то, что сделал xtravar.

Я добавил представление вне иерархии представлений tableViewController. hierarchy

Затем я использовал следующий код в 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

В основном я добавил emptyStateView в качестве поднабора объекта tableView. Поскольку разделители будут перекрывать представление, я устанавливаю их цвет на clearColor. Чтобы вернуться к цвету разделителя по умолчанию, вы можете просто установить его на nil.

Ответ 10

Использование Container View Controller - правильный способ сделать это в соответствии с Apple.

Я помещаю все мои пустые состояния в отдельную раскадровку. Каждый из них имеет собственный подкласс UIViewController. Я добавляю контент прямо под их корневым представлением. Если требуется какое-либо действие/кнопка, теперь у вас уже есть контроллер для ее обработки.
Тогда это просто вопрос создания требуемого контроллера представлений из этой раскадровки, добавьте его в качестве контроллера детского представления и добавьте представление контейнера в иерархию tableView (под просмотр). Ваш пустой вид состояния также будет прокручиваться, что будет хорошо, и вы сможете реализовать pull для обновления.

Ознакомьтесь с главой "Добавление контроллера детского просмотра на ваш контент" для получения справки о том, как реализовать.

Просто убедитесь, что вы установите кадр детского просмотра как (0, 0, tableView.frame.width, tableView.frame.height), и все будет центрировано и выровнено правильно.

Ответ 11

Во-первых, проблемы с другими популярными подходами.

BackgroundView

Фоновый вид не центрируется, если вы использовали простой случай установки UILabel.

Ячейки, заголовки или нижние колонтитулы для отображения сообщения

Это мешает вашему функциональному коду и вводит странные краевые случаи. Если вы хотите полностью сконцентрировать свое сообщение, это добавит еще один уровень сложности.

Перемещение вашего собственного контроллера табличного представления

Вы теряете встроенные функции, такие как refreshControl и повторно изобретаете колесо. Придерживайтесь UITableViewController для получения наилучших поддерживаемых результатов.

Добавление UITableViewController в качестве контроллера детского представления

У меня возникло ощущение, что в конце концов вы столкнетесь с проблемами с содержимым в iOS 7+ - плюс, зачем усложнять?

Мое решение

Лучшее решение, которое я придумал (и, предоставлено, это не идеально), - это сделать особый вид, который может сидеть поверх прокрутки и действовать соответствующим образом. Это явно усложняется в iOS 7 с безумством contentInset, но это выполнимо.

Вещи, на которые вы должны обратить внимание:

  • разделители таблиц попадают на передний план в некоторый момент во время reloadData - вам нужно защититься от этого
  • contentInset/contentOffset - соблюдайте эти клавиши в своем специальном представлении
  • клавиатура - если вы не хотите, чтобы клавиатура мешала, этот другой расчет
  • autolayout - вы не можете зависеть от изменений кадра для позиционирования вашего представления.

Как только вы это разобрали один раз в одном подклассе UIView, вы можете использовать его для всего: загрузка роликов, отключение представлений, отображение сообщений об ошибках и т.д.

Ответ 12

Это лучшее и простое решение.

    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;

Ответ 13

Показать сообщение для пустого списка, его UITableView или 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
        }
    }
}

Обычаи:

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

ИЛИ ЖЕ:

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

Помните: не забудьте удалить метку сообщения, если данные появятся после обновления.

Ответ 14

Выберите сцену TableviewController в раскадровке

введите описание изображения здесь

Перетащите UIView Добавьте ярлык с вашим сообщением (например: Нет данных)

введите описание изображения здесь

создать выход из UIView (скажем, например, yournoDataView) на вашем TableViewController.

и в viewDidLoad

self.tableView.backgroundView = yourNoDataView

Ответ 15

Быстрая версия, но более простая и простая форма. ** 3.0

Надеюсь, это сервер вашей цели......

В вашем 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
        }
    }
}

Класс помощника с функцией:

 /* 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;
        }

Ответ 16

Наверное, это не самое лучшее решение, но я сделал это, просто поместив метку в нижней части моей таблицы, и если строки = 0, я присваиваю ей некоторый текст. Довольно легко и достигается то, что вы пытаетесь сделать с помощью нескольких строк кода.

У меня есть два раздела в моей таблице (рабочие места и школы)

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    if (jobs.count == 0 && schools.count == 0) {
        emptyLbl.text = "No jobs or schools"
    } else {
        emptyLbl.text = ""
    }

Ответ 17

Самый простой и быстрый способ сделать это - перетащить метку на боковую панель под tableView. Создайте выход для метки и tableView и добавьте оператор if, чтобы скрыть и показать метку и таблицу по мере необходимости. В качестве альтернативы вы можете добавить tableView.tableFooterView = UIView (frame: CGRect.zero) к вам viewDidLoad(), чтобы дать пустой таблице представление о том, что она скрыта, если таблица и фоновое представление имеют одинаковый цвет.

Ответ 18

Дайте тогда проблемы с разметкой меток (не говоря уже о переводе сообщения!). Мне очень нравится это простое решение - просто скрыть (или показать) разделители ячеек после обновления модели:

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

Также в раскадровке установите таблицу, чтобы не было разделителя для первого появления.

Ответ 19

Использование 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
}