Manejo de un UITableView vacío. Imprima un post amistoso

Tengo una UITableView que en algunos casos es legal estar vacía. Entonces, en lugar de mostrar la image de background de la aplicación, preferiría imprimir un post amistoso en la pantalla, como por ejemplo:

Esta list ahora está vacía.

¿Cuál es la forma más sencilla de hacerlo?

La propiedad backgroundView de UITableView es tu amiga.

En viewDidLoad o en cualquier lugar donde reloadData , debe determinar si su tabla está vacía o no y actualizar la propiedad backgroundView de UITableView con una UIView que contenga una UILabel o simplemente establecerla en nil. Eso es.

Por supuesto, es posible hacer que la fuente de datos de UITableView cumpla doble function y devuelva una celda especial de "list está vacía", me parece una especie de kludge. De repente, la numberOfRowsInSection:(NSInteger)section tiene que calcular el número de filas de otras secciones sobre las que no se preguntó para asegurarse de que están vacías también. También debe crear una celda especial que tenga el post vacío. Tampoco olvide que es probable que necesite cambiar la altura de su celda para acomodar el post vacío. Todo esto es factible, pero parece que se trata de un parche sobre el parche.

Basado en las respuestas aquí, aquí hay una class rápida que hice que puedes utilizar en tu UITableViewController .

 import Foundation import UIKit class TableViewHelper { class func EmptyMessage(message:String, viewController:UITableViewController) { let messageLabel = UILabel(frame: CGRectMake(0,0,viewController.view.bounds.size.width, viewController.view.bounds.size.height)) 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; } } 

En su UITableViewController puede llamar a esto en 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 } } 

introduzca la descripción de la imagen aquí

Con un poco de ayuda de http://www.appcoda.com/pull-to-refresh-fitableview-empty/

Una forma de hacerlo sería modificar su origen de datos para devolver 1 cuando el número de filas sea cero y para producir una celda de propósito especial (quizás con un identificador de celda diferente) en el 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 ... } 

Esto puede complicarse un poco si tiene varios controlleres de vista de tabla que necesita mantener, porque alguien eventualmente olvidará insert un cheque cero. Un mejor enfoque es crear una implementación separada de una implementación UITableViewDataSource que siempre devuelve una sola fila con un post configurable (llamémosle EmptyTableViewDataSource ). Cuando los datos que maneja su controller de vista de tabla cambian, el código que maneja el cambio verificará si los datos están vacíos. Si no está vacío, configure su controller de vista de tabla con su fuente de datos regular; de lo contrario, EmptyTableViewDataSource con una instancia de EmptyTableViewDataSource que se haya configurado con el post apropiado.

Recomiendo la siguiente biblioteca: DZNEmptyDataSet

La forma más fácil de agregarlo a su proyecto es usarlo con Cocaopods, así: pod 'DZNEmptyDataSet'

En su TableViewController agregue la siguiente statement de import (Swift):

 import DZNEmptyDataSet 

Luego, asegúrese de que su class cumpla con el DNZEmptyDataSetSource y DZNEmptyDataSetDelegate así:

 class MyTableViewController: UITableViewController, DZNEmptyDataSetSource, DZNEmptyDataSetDelegate 

En su vista, viewDidLoad agrega las siguientes líneas de código:

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

Ahora todo lo que tiene que hacer para mostrar el estado vacío es:

 //Add title for empty dataset func titleForEmptyDataSet(scrollView: UIScrollView!) -> NSAttributedString! { let str = "Welcome" let attrs = [NSFontAttributeName: UIFont.prefernetworkingFontForTextStyle(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.prefernetworkingFontForTextStyle(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.prefernetworkingFontForTextStyle(UIFontTextStyleCallout)] return NSAttributedString(string: str, attributes: attrs) } //Add action for button func emptyDataSetDidTapButton(scrollView: UIScrollView!) { let ac = UIAlertController(title: "Button tapped!", message: nil, prefernetworkingStyle: .Alert) ac.addAction(UIAlertAction(title: "Hurray", style: .Default, handler: nil)) presentViewController(ac, animated: true, completion: nil) } 

Estos methods no son obligatorios, también es posible mostrar el estado vacío sin un button, etc.

Para 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.prefernetworkingFont(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.prefernetworkingFont(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.prefernetworkingFont(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, prefernetworkingStyle: .alert) ac.addAction(UIAlertAction(title: "Hurray", style: .default, handler: nil)) present(ac, animated: true, completion: nil) } 

He estado usando el post titleForFooterInSection para esto. No sé si esto es subóptimo o no, pero funciona.

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

Usar backgroundView está bien, pero no se despliega muy bien como en Mail.app.

Hice algo similar a lo que hizo xtravar .

tableViewController una vista fuera de la jerarquía de vista de tableViewController . jerarquía

Luego utilicé el siguiente código en 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 

Básicamente agregué emptyStateView como una tableView object tableView . Como los separadores se superpondrían a la vista, establecí su color en clearColor . Para volver al color del separador pnetworkingeterminado, puede configurarlo a nil .

Primero, los problemas con otros enfoques populares.

BackgroundView

La vista de background no se centra bien si utilizas el simple caso de configurarlo para que sea un UILabel.

Células, encabezados o pies de página para mostrar el post

Esto interfiere con su código funcional e introduce casos de borde extraños. Si desea centrar perfectamente su post, eso agrega otro nivel de complejidad.

Rodando su propio controller de vista de tabla

Pierde la funcionalidad integrada, como refreshControl, y reinventa la rueda. Pegue a UITableViewController para get los mejores resultados mantenibles.

Agregar UITableViewController como un controller de vista secundario

Tengo la sensación de que terminarás con problemas de ContentInset en iOS 7+, ¿y por qué complicar las cosas?

Mi solución

La mejor solución que he encontrado (y, por supuesto, esto no es ideal) es hacer una vista especial que se pueda sentar encima de una vista de desplazamiento y que actúe en consecuencia. Esto obviamente se complica en iOS 7 con la locura de contenido de inicio, pero es factible.

Cosas por las que debes cuidarte:

  • los separadores de tablas se llevan al frente en algún momento durante reloadData: necesitas protegerte contra eso
  • contentInset / contentOffset: observa esas teclas en tu vista especial
  • keyboard: si no quieres que el keyboard se interponga, ese es otro cálculo
  • autolayout: no puede depender de los cambios de marco para posicionar su vista

Una vez que haya desencryption esta información una vez en una subclass UIView, puede usarla para todo: cargar hiladores, desactivar vistas, mostrar posts de error, etc.

Solo puedo recomendar drag and drop un UITextView dentro de TableView después de las celdas. Realice una connection al ViewController y oculte / muestre cuando sea apropiado (p. Ej., Cada vez que se vuelve a cargar la tabla).

introduzca la descripción de la imagen aquí

Igual que la respuesta de Jhonston, pero la preferí como una extensión:

 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 restre() { self.backgroundView = nil self.separatorStyle = .singleLine } } 

Uso:

 override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if things.count == 0 { self.tableView.setEmptyMessage("My Message") } else { self.tableView.restre() } return things.count } 

El uso de un Controlador de vista de contenedor es la forma correcta de hacerlo de acuerdo con Apple .

Puse todas mis vistas de estado vacío en un Storyboard por separado. Cada uno debajo de su propia subclass UIViewController. Agrego contenido directamente debajo de su vista raíz. Si se necesita cualquier acción / button, ya tiene un controller para manejarlo.
Entonces, es solo cuestión de instanciar el controller de vista deseado desde ese Storyboard, agregarlo como un controller de visualización secundaria y agregar la vista de contenedor a la jerarquía de tableView (subvisualización). También se puede desplazar su vista de estado vacía, que se siente bien y le permite implementar la extracción para actualizar.

Lea el capítulo 'Agregar un controller de vista secundaria a su contenido' para get ayuda sobre cómo implementar.

Simplemente asegúrese de configurar el marco de vista secundario como (0, 0, tableView.frame.width, tableView.frame.height) y las cosas se centrarán y alinearán correctamente.

Esta es la solución mejor y más simple.

  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; 

Versión Swift pero mejor y más simple. ** 3.0

Espero que sirva tu propósito …

En tu UITableViewController.

 override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if searchController.isActive && searchController.searchBar.text != "" { if filtenetworkingContacts.count > 0 { self.tableView.backgroundView = .none; return filtenetworkingContacts.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 } } } 

Clase auxiliar con 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(networking: 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; } 

Probablemente no sea la mejor solución, pero lo hice simplemente poniendo una label en la parte inferior de mi tabla y si las filas = 0, entonces le asigno un poco de text. Bastante fácil, y logra lo que está tratando de hacer con unas pocas líneas de código.

Tengo dos secciones en mi table (trabajos y escuelas)

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if (jobs.count == 0 && schools.count == 0) { emptyLbl.text = "No jobs or schools" } else { emptyLbl.text = "" } 

Seleccione su escena tableviewController en el guión gráfico

introduzca la descripción de la imagen aquí

Arrastre y suelte UIView Agregue label con su post (por ejemplo: Sin datos)

introduzca la descripción de la imagen aquí

cree una salida de UIView (por ejemplo, por ejemplo, yournoDataView) en su TableViewController.

y en viewDidLoad

self.tableView.backgroundView = yourNoDataView