J'ai un UITableView que dans certains cas, il est légal d'être vide. Donc, au lieu d'afficher l'image de fond de l'application, je préférerais imprimer un message convivial à l'écran, tel que:
Cette liste est maintenant vide
Quel est le moyen le plus simple de le faire?
La propriété backgroundView de UITableView est votre ami.
Dans viewDidLoad
ou n'importe où vous reloadData
, vous devez déterminer si votre table est vide ou non et mettre à jour la propriété backgroundView de UITableView avec un UIView contenant un UILabel ou simplement le définir sur nil. C'est ça.
Il est bien sûr possible de faire en sorte que la source de données de UITableView fasse un double travail et renvoie une cellule spéciale "la liste est vide", cela me semble être un kludge. Soudain, numberOfRowsInSection:(NSInteger)section
doit calculer le nombre de lignes des autres sections sur lesquelles il n'a pas été interrogé pour s'assurer qu'elles sont également vides. Vous devez également créer une cellule spéciale contenant le message vide. N'oubliez pas non plus que vous devez probablement modifier la hauteur de votre cellule pour prendre en charge le message vide. Tout cela est faisable, mais cela ressemble à du pansement par-dessus le pansement.
Sur la base des réponses fournies ici, voici un cours rapide que j'ai créé et que vous pouvez utiliser dans votre 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;
}
}
Dans votre UITableViewController
, vous pouvez appeler cela dans 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
}
}
Avec un peu d'aide de http://www.appcoda.com/pull-to-refresh-uitableview-empty/
Identique à la réponse de Jhonston, mais je l’ai préférée comme extension:
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
}
}
Usage:
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
}
Je recommande la bibliothèque suivante: DZNEmptyDataSet
Le moyen le plus simple de l’ajouter à votre projet est de l’utiliser avec des Cocaopod comme ceci: pod 'DZNEmptyDataSet'
Dans votre TableViewController, ajoutez l'instruction d'importation suivante (Swift):
import DZNEmptyDataSet
Ensuite, assurez-vous que votre classe est conforme à DNZEmptyDataSetSource
et DZNEmptyDataSetDelegate
comme ceci:
class MyTableViewController: UITableViewController, DZNEmptyDataSetSource, DZNEmptyDataSetDelegate
Dans votre viewDidLoad
ajoutez les lignes de code suivantes:
tableView.emptyDataSetSource = self
tableView.emptyDataSetDelegate = self
tableView.tableFooterView = UIView()
Maintenant, tout ce que vous avez à faire pour montrer le vidage est:
//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)
}
Ces méthodes ne sont pas obligatoires, il est également possible d'afficher l'état vide sans bouton, etc.
Pour 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)
}
Une façon de le faire serait de modifier votre source de données pour renvoyer 1
lorsque le nombre de lignes est égal à zéro et pour produire une cellule à usage spécifique (avec éventuellement un identificateur de cellule différent) dans la tableView:cellForRowAtIndexPath:
méthode.
-(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
...
}
Cela peut devenir quelque peu compliqué si vous devez gérer plusieurs contrôleurs de vue de table, car quelqu'un finira par oublier d'insérer un contrôle zéro. Une meilleure approche consiste à créer une implémentation séparée d'une implémentation UITableViewDataSource
qui renvoie toujours une seule ligne avec un message configurable (appelons-le EmptyTableViewDataSource
). Lorsque les données gérées par votre contrôleur d'affichage de table changent, le code qui gère la modification vérifie si les données sont vides. S'il n'est pas vide, définissez votre contrôleur d'affichage de table avec sa source de données standard; sinon, définissez-le avec une instance de EmptyTableViewDataSource
qui a été configurée avec le message approprié.
J'ai utilisé le message titleForFooterInSection pour cela. Je ne sais pas si c'est sous-optimal ou non, mais ça marche.
-(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;
}
Donc, pour une solution plus sûre:
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
}
}
et pour UICollectionView
aussi:
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
}
}
Solution plus générique:
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
}
}
L'utilisation de backgroundView convient, mais elle ne défile pas correctement, comme dans Mail.app.
J'ai fait quelque chose de similaire à ce que xtravar a fait.
J'ai ajouté une vue en dehors de la hiérarchie de vues du tableViewController
.
Ensuite, j'ai utilisé le code suivant dans 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
Fondamentalement, j'ai ajouté le emptyStateView
en tant que sous-vue de l'objet tableView
. Comme les séparateurs chevaucheraient la vue, j'ai défini leur couleur sur clearColor
. Pour revenir à la couleur de séparation par défaut, vous pouvez simplement la définir sur nil
.
Utiliser un contrôleur de vue de conteneur est la bonne façon de procéder selon Apple .
J'ai mis toutes mes vues d'état vides dans un Storyboard séparé. Chacune de ses sous-classes UIViewController. J'ajoute du contenu directement sous leur vue racine. Si une action/un bouton est nécessaire, vous avez déjà un contrôleur pour le gérer.
Ensuite, il suffit d'instancier le contrôleur de vue souhaité à partir de ce Storyboard, de l'ajouter en tant que contrôleur de vue enfant et d'ajouter la vue du conteneur à la hiérarchie du tableau (vue secondaire). Votre vue d'état vide sera également défilable, ce qui sera agréable et vous permettra de mettre en œuvre l'extraction pour l'actualisation.
Lire chapitre 'Ajout d'un contrôleur de vue enfant à votre contenu' pour obtenir de l'aide sur la mise en œuvre.
Assurez-vous simplement que vous définissez le cadre de vue enfant sur (0, 0, tableView.frame.width, tableView.frame.height)
et les choses seront centrées et alignées correctement.
Premièrement, les problèmes avec d’autres approches populaires.
BackgroundView
La vue en arrière-plan ne serait pas bien centrée si vous utilisiez le simple cas de le définir pour qu'il soit un UILabel.
Cellules, en-têtes ou pieds de page pour afficher le message
Cela interfère avec votre code fonctionnel et introduit des cas étranges Edge. Si vous voulez centrer parfaitement votre message, cela ajoute un autre niveau de complexité.
Rouler votre propre contrôleur de vue de table
Vous perdez des fonctionnalités intégrées, telles que refreshControl, et réinventez la roue. Adhérez à UITableViewController pour obtenir les meilleurs résultats possibles.
Ajout de UITableViewController en tant que contrôleur de vue enfant
J'ai l'impression que vous allez vous retrouver avec des problèmes de contentInset dans iOS 7+ - et pourquoi compliquer les choses?
Ma solution
La meilleure solution que j'ai proposée (et, d'accord, ce n'est pas idéal) est de créer une vue spéciale pouvant s'asseoir sur une vue de défilement et agir en conséquence. Cela se complique évidemment dans iOS 7 avec la folie contentInset, mais c'est faisable.
Choses que vous devez surveiller:
Une fois que vous avez compris cela une fois dans une sous-classe UIView, vous pouvez l’utiliser pour tout: charger les fileurs, désactiver les vues, afficher les messages d’erreur, etc.
C'est la solution la meilleure et la plus 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;
Afficher le message pour une liste vide, que ce soit ITableView ou ICollectionView.
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
}
}
}
sages:
if cellsViewModels.count == 0 {
self.tableView.showEmptyListMessage("No Product In List!")
}
OR:
if cellsViewModels.count == 0 {
self.collectionView?.showEmptyListMessage("No Product In List!")
}
Rappelez-vous: N'oubliez pas de supprimer l'étiquette du message au cas où les données viendraient après l'actualisation.
Utilisation de 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
}
Ce n’est probablement pas la meilleure solution, mais j’ai fait cela en mettant simplement une étiquette au bas de mon tableau et si les lignes = 0, je lui ai assigné du texte. Assez facile, et réalise ce que vous essayez de faire avec quelques lignes de code.
J'ai deux sections dans mon tableau (emplois et écoles)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (jobs.count == 0 && schools.count == 0) {
emptyLbl.text = "No jobs or schools"
} else {
emptyLbl.text = ""
}
J'ai fait quelques modifications pour que nous n'ayons pas besoin de vérifier le nombre manuellement, mais j'ai également ajouté des contraintes pour l'étiquette afin que rien ne se passe mal, quelle que soit la taille du message, comme indiqué ci-dessous:
extension UITableView {
fileprivate func configureLabelLayout(_ messageLabel: UILabel) {
messageLabel.translatesAutoresizingMaskIntoConstraints = false
let labelTop: CGFloat = CGFloat(UIDevice.current.userInterfaceIdiom == .pad ? 25:15)
messageLabel.topAnchor.constraint(equalTo: backgroundView?.topAnchor ?? NSLayoutAnchor(), constant: labelTop).isActive = true
messageLabel.widthAnchor.constraint(equalTo: backgroundView?.widthAnchor ?? NSLayoutAnchor(), constant: -20).isActive = true
messageLabel.centerXAnchor.constraint(equalTo: backgroundView?.centerXAnchor ?? NSLayoutAnchor(), constant: 0).isActive = true
}
fileprivate func configureLabel(_ message: String) {
let messageLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height))
messageLabel.textColor = .black
messageLabel.numberOfLines = 0
messageLabel.textAlignment = .center
let fontSize = CGFloat(UIDevice.current.userInterfaceIdiom == .pad ? 25:15)
let font: UIFont = UIFont(name: "MyriadPro-Regular", size: fontSize) ?? UIFont()
messageLabel.font = font
messageLabel.text = message
self.backgroundView = UIView()
self.backgroundView?.addSubview(messageLabel)
configureLabelLayout(messageLabel)
self.separatorStyle = .none
}
func setEmptyMessage(_ message: String, _ isEmpty: Bool) {
if isEmpty { // instead of making the check in every TableView DataSource in the project
configureLabel(message)
}
else {
restore()
}
}
func restore() {
self.backgroundView = nil
self.separatorStyle = .singleLine
}
}
tilisation
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let message: String = "The list is empty."
ticketsTableView.setEmptyMessage(message, tickets.isEmpty)
return self.tickets.count
}
version Swift mais une forme meilleure et plus simple. ** 3.
J'espère que cela servira votre but ......
Dans votre 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
}
}
}
Classe d'assistance avec fonction:
/* 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;
}
Pour ce faire, le moyen le plus simple et le plus rapide consiste à faire glisser une étiquette sur le panneau latéral sous tableView. Créez un point de vente pour l’étiquette et la table et ajoutez une instruction if pour masquer et afficher l’étiquette et la table selon vos besoins. Vous pouvez également ajouter tableView.tableFooterView = UIView (frame: CGRect.zero), ce qui vous le permettra viewDidLoad () pour donner à un tableau vide la perception qu'il est masqué si le tableau et la vue d'arrière-plan ont la même couleur.