2013-04-01 2 views
63

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

Этот список теперь пуст

Что это самый простой способ сделай это?

+8

Проверьте этот проект: https://github.com/dzenbot/DZNEmptyDataSet – oleynikd

ответ

112

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

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

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

+5

Фоновый просмотр - лучшее решение, я думаю. Спасибо! –

+0

Этот anwser должен быть принят как правильный! – Kosmetika

+1

Одним из недостатков является то, что при разрыве табличного сообщения сообщение остается на своем месте. Я бы предпочел, чтобы в приложении App Store появилось обновление. Здесь пустое сообщение идет с поведением прокрутки ... – testing

12

Один из способов сделать это - изменить ваш источник данных, чтобы вернуть 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, который был настроен с соответствующим сообщением.

+4

I «У нас были проблемы с таблицами, у которых были удалены строки с помощью« deleteRowsAtIndexPaths: withRowAnimation: », поскольку число, возвращаемое из« numberOfRowsInSection », должно соответствовать результату $ numRows - $ rowsDeleted. – Animal451

+3

Я очень, очень рекомендую против этого. Это задирает ваш код краями. – xtravar

+2

@xtravar Вместо того, чтобы downvoting, подумайте о публикации своего собственного ответа. – dasblinkenlight

2

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

BackgroundView

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

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

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

Подвижной свой собственный контроллер табличное

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

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

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

Мое решение

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

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

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

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

+0

Можете ли вы уточнить в своем ответе. Как вы узнаете, что таблица пуста? Затем «действовать соответствующим образом». –

+0

Вы знаете, что ваш стол пуст, потому что вы заселяете свой стол. Поэтому в вашем контроллере просмотра у вас будет асинхронный обратный вызов загрузки. В этом обратном вызове if (itemsToShow.count == 0) {добавьте ваше представление в список прокрутки}. Сложная часть делает этот вид сверху («экран/вид сообщения») позиционированным, чтобы охватить весь кадр прокрутки, минус contentInset и т. Д., Чтобы сообщение было центрировано по вертикали. – xtravar

+0

Как добавить представление в верхней части таблицы? self.view - это представление таблицы, и если я использую 'addSubView', он привязан к представлению таблицы, который всегда создает проблемы с автоматическим расположением. – testing

8

Я использую сообщение 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; 
} 
+2

Приятный трюк и кажется мне довольно чистым. Вот один лайнер: 'return tableView.numberOfRowsInSection (section) == 0? «Этот список пуст»: nil' – TruMan1

5

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

enter image description here

+0

Это самый простой способ)! – moonvader

+0

Такая болезненная головная боль - путь яблока; спасибо за эту идею – Eenvincible

47

На основе ответов здесь, вот быстрый класс я сделал, что вы можете использовать в вашем 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/

+0

также хотел бы добавить к ней изображение. Но очень быстрое решение. – Annjawn

10

рекомендую следующую библиотеку: 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 3

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

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

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

//Add your button 
func buttonTitle(forEmptyDataSet scrollView: UIScrollView!, for state: UIControlState) -> NSAttributedString! { 
    let str = "Add Grokkleglob" 
    let attrs = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: UIFontTextStyle.callout)] 
    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)) 
    present(ac, animated: true, completion: nil) 
} 
3

Используя backgroundView это хорошо, но это не scrol l хорошо нравится в 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.

+0

Это работает очень хорошо! Но если у вас есть элементы для загрузки, например, это не прокручивается вместе с ними.Мне было лучше установить «tableHeaderView» и убедиться, что [он изменяет размер] (http://stackoverflow.com/questions/5581116/how-to-set-the-height-of-table-header-in -uitableview). – Hannele

0

Быстрая версия, но лучше и проще. ** 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; 
     } 
2

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

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

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

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

0

Возможно, это не самое большое решение, но я сделал это, просто поместив метку в нижнюю часть таблицы, и если строки = 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 = "" 
    } 
+0

Вы также можете заменить ярлык пустым изображением, если count = 0, если хотите. – Zach

5

То же ответ 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 
} 
1

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

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; 
0

Выберите tableviewController Сцена в раскадровке

enter image description here

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

enter image description here

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

и viewDidLoad

self.tableView.backgroundView = yourNoDataView

1

Таким образом, для обеспечения более безопасного решения:

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 
    } 
} 
Смежные вопросы