ios 处理空的 UITableView。打印友好信息

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15746745/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 22:59:14  来源:igfitidea点击:

Handling an empty UITableView. Print a friendly message

iphoneiosuitableviewuiview

提问by cateof

I have a UITableView that in some cases it is legal to be empty. So instead of showing the background image of the app, I would prefer to print a friendly message in the screen, such as:

我有一个 UITableView,在某些情况下它是空的。因此,与其显示应用程序的背景图像,不如在屏幕上打印一条友好的消息,例如:

This list is now empty

这个列表现在是空的

What is the simplest way to do it?

最简单的方法是什么?

回答by Ray Fix

UITableView's backgroundView property is your friend.

UITableView 的 backgroundView 属性是你的朋友。

In viewDidLoador anywhere that you reloadDatayou should determine if there your table is empty or not and update the UITableView's backgroundView property with a UIView containing a UILabel or just set it to nil. That's it.

在您应该确定表格是否为空的viewDidLoad地方或任何地方,reloadData并使用包含 UILabel 的 UIView 更新 UITableView 的 backgroundView 属性,或者将其设置为 nil。就是这样。

It is of course possible to make UITableView's data source do double duty and return a special "list is empty" cell, it strikes me as a kludge. Suddenly numberOfRowsInSection:(NSInteger)sectionhas to compute the number of rows of other sections it wasn't asked about to make sure they are empty too. You also need to make a special cell that has the empty message. Also don't forget that you need to probably change the height of your cell to accommodate the empty message. This is all doable but it seems like band-aid on top of band-aid.

当然可以让 UITableView 的数据源执行双重任务并返回一个特殊的“列表为空”单元格,这让我觉得很麻烦。突然numberOfRowsInSection:(NSInteger)section必须计算它没有被询问的其他部分的行数,以确保它们也是空的。您还需要制作一个带有空消息的特殊单元格。另外不要忘记,您可能需要更改单元格的高度以容纳空消息。这一切都是可行的,但似乎是创可贴之上的创可贴。

回答by Johnston

Based on the answers here, here is a quick class I made that you can use on in your UITableViewController.

根据这里的答案,这是我制作的一个快速课程,您可以在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;
    }
}

In your UITableViewControlleryou can call this in numberOfSectionsInTableView(tableView: UITableView) -> Int

在你的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

在此处输入图片说明

With a little help from http://www.appcoda.com/pull-to-refresh-uitableview-empty/

http://www.appcoda.com/pull-to-refresh-uitableview-empty/ 的帮助下

回答by Frankie

Same as Jhonston's answer, but I preferred it as an extension:

与 Jhonston 的答案相同,但我更喜欢将其作为扩展名:

import UIKit

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
}

回答by Hapeki

I recommend the following library: DZNEmptyDataSet

我推荐以下库:DZNEmptyDataSet

The easiest way to add it in your project is to use it with Cocaopods like so: pod 'DZNEmptyDataSet'

将它添加到项目中的最简单方法是将它与 Cocaopods 一起使用,如下所示: pod 'DZNEmptyDataSet'

In your TableViewController add the following import statement (Swift):

在您的 TableViewController 中添加以下导入语句 (Swift):

import DZNEmptyDataSet

Then make sure your class conforms to the DNZEmptyDataSetSourceand DZNEmptyDataSetDelegatelike so:

然后确保您的班级符合DNZEmptyDataSetSourceDZNEmptyDataSetDelegate像这样:

class MyTableViewController: UITableViewController, DZNEmptyDataSetSource, DZNEmptyDataSetDelegate

In your viewDidLoadadd the following lines of code:

在您viewDidLoad添加以下代码行:

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

Now all you have to do to show the emptystate is:

现在你需要做的就是显示空状态:

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

These methods aren't mandatory, it's also possible to just show the empty state without a button etc.

这些方法不是强制性的,也可以只显示空状态而没有按钮等。

For Swift 4

对于 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)
}

回答by dasblinkenlight

One way of doing it would be modifying your data source to return 1when the number of rows is zero, and to produce a special-purpose cell (perhaps with a different cell identifier) in the tableView:cellForRowAtIndexPath:method.

一种方法是修改数据源以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
    ...
}

This may get somewhat complicated if you have multiple table view controllers that you need to maintain, because someone will eventually forget to insert a zero check. A better approach is to create a separate implementation of a UITableViewDataSourceimplementation that always returns a single row with a configurable message (let's call it EmptyTableViewDataSource). When the data that is managed by your table view controller changes, the code that manages the change would check if the data is empty. If it is not empty, set your table view controller with its regular data source; otherwise, set it with an instance of the EmptyTableViewDataSourcethat has been configured with the appropriate message.

如果您需要维护多个表视图控制器,这可能会变得有些复杂,因为有人最终会忘记插入零检查。更好的方法是创建一个单独的实现,该UITableViewDataSource实现始终返回带有可配置消息的单行(我们称之为EmptyTableViewDataSource)。当表视图控制器管理的数据发生更改时,管理更改的代码将检查数据是否为空。如果它不为空,请使用其常规数据源设置您的表视图控制器;否则,使用EmptyTableViewDataSource已配置适当消息的 实例设置它。

回答by Carlos B

I have been using the titleForFooterInSection message for this. I don't know if this is suboptimal or not, but it works.

为此,我一直在使用 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;
}

回答by Meseery

So for a safer solution:

因此,为了更安全的解决方案:

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

and for UICollectionViewas well:

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

More Generic Solution:

更通用的解决方案:

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

回答by pasql

I can only recommend to drag&drop a UITextView inside the TableView after the cells. Make a connection to the ViewController and hide/display it when appropriate (e.g. whenever the table reloads).

我只能建议在单元格之后的 TableView 中拖放一个 UITextView 。连接到 ViewController 并在适当的时候隐藏/显示它(例如,每当表重新加载时)。

enter image description here

在此处输入图片说明

回答by mangerlahn

Using the backgroundView is fine, but it does not scroll nicely like in Mail.app.

使用 backgroundView 很好,但它不像在 Mail.app 中那样滚动得很好。

I did something similar to what xtravardid.

我做了一些类似于xtravar所做的事情。

I added a view outside the view hierarchy of the tableViewController. hierarchy

我在tableViewController. 等级制度

Then i used the following code in tableView:numberOfRowsInSection::

然后我在以下代码中使用了以下代码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

Basically I added the emptyStateViewas a subview of the tableViewobject. As the separators would overlap the view, I set their color to clearColor. To get back to the default separator color, you can just set it to nil.

基本上我添加了emptyStateView作为tableView对象的子视图。由于分隔符会与视图重叠,因此我将它们的颜色设置为clearColor。要恢复默认分隔符颜色,您只需将其设置为nil.

回答by AmitP

Using a Container View Controller is the right way to do it according to Apple.

根据Apple 的说法,使用 Container View Controller 是正确的方法。

I put all my empty state views in a separate Storyboard. Each under it's own UIViewController subclass. I add content directly under their root view. If any action/button is needed, you now already have a controller to handle it.
Then its just a matter of instantiating the desired view controller from that Storyboard, add it as a child view controller and add the container view to the tableView's hierarchy (sub view). Your empty state view will be scrollable as well, which feels good and allow you to implement pull to refresh.

我把我所有的空状态视图放在一个单独的故事板中。每个在它自己的 UIViewController 子类下。我直接在他们的根视图下添加内容。如果需要任何操作/按钮,您现在已经有了一个控制器来处理它。
然后只需从该 Storyboard 实例化所需的视图控制器,将其添加为子视图控制器并将容器视图添加到 tableView 的层次结构(子视图)。你的空状态视图也将是可滚动的,这感觉很好,并允许你实现拉动刷新。

Readchapter 'Adding a Child View Controller to Your Content'for help on how to implement.

阅读“将子视图控制器添加到您的内容”一获取有关如何实施的帮助。

Just make sure you set the child view frame as (0, 0, tableView.frame.width, tableView.frame.height)and things will be centered and aligned properly.

只需确保您将子视图框架设置为这样 (0, 0, tableView.frame.width, tableView.frame.height),事情就会正确居中和对齐。