处理空的UITableView.打印友好的信息

cat*_*eof 110 iphone uitableview uiview ios

我有一个UITableView,在某些情况下,空是合法的.因此,我宁愿在屏幕上打印友好的消息,而不是显示应用程序的背景图像,例如:

此列表现在为空

最简单的方法是什么?

Ray*_*Fix 160

UITableView的backgroundView属性是你的朋友.

viewDidLoad或任何地方,你reloadData,你应该确定是否有你的表是空的或不并用含有一个UILabel一个UIView更新的UITableView的backgroundView财产或只是将其设置为nil.而已.

当然可以让UITableView的数据源执行双重任务并返回一个特殊的"列表为空"单元格,它让我感觉像一个kludge.突然numberOfRowsInSection:(NSInteger)section必须计算其他部分的行数,而不是要求确保它们也是空的.您还需要创建一个具有空消息的特殊单元格.另外,请不要忘记您可能需要更改单元格的高度以容纳空消息.这一切都是可行的,但它看起来像创可贴上的创可贴.

  • 我认为backgroundView是最好的解决方案.谢谢! (9认同)

Joh*_*ton 81

根据这里的答案,这里有一个快速的课程,你可以在你的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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
    }
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

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

  • 使用扩展而不是类(不要传递视图控制器) (7认同)

Fra*_*kie 59

与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
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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
}
Run Code Online (Sandbox Code Playgroud)

  • 如果您有部分,那么您需要将此逻辑移至 `func numberOfSections(in tableView: UITableView) -> Int` 方法 (3认同)
  • 扩展更好!谢谢。 (2认同)

Hap*_*eki 14

我推荐以下库:DZNEmptyDataSet

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

在TableViewController中添加以下import语句(Swift):

import DZNEmptyDataSet
Run Code Online (Sandbox Code Playgroud)

然后确保你的类符合DNZEmptyDataSetSourceDZNEmptyDataSetDelegate像这样:

class MyTableViewController: UITableViewController, DZNEmptyDataSetSource, DZNEmptyDataSetDelegate
Run Code Online (Sandbox Code Playgroud)

在您viewDidLoad添加以下代码行:

tableView.emptyDataSetSource = self
tableView.emptyDataSetDelegate = self
tableView.tableFooterView = UIView()
Run Code Online (Sandbox Code Playgroud)

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

//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)
}
Run Code Online (Sandbox Code Playgroud)

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

对于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)
}
Run Code Online (Sandbox Code Playgroud)


das*_*ght 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
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

  • 我在使用`deleteRowsAtIndexPaths:withRowAnimation:`删除行的表时遇到了问题,因为从`numberOfRowsInSection`返回的数字需要匹配$ numRows的结果 - $ rowsDeleted. (4认同)
  • 我强烈建议不要这样做.它通过边缘情况谜语你的代码. (4认同)
  • @xtravar而不是低估,考虑发布自己的答案. (2认同)

Car*_*s B 10

我一直在使用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;
}
Run Code Online (Sandbox Code Playgroud)

  • 好戏,对我来说似乎很干净.这是一个单行:`return tableView.numberOfRowsInSection(section)== 0?"这个列表现在是空的":nil` (2认同)

Mes*_*ery 7

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

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
  }
}
Run Code Online (Sandbox Code Playgroud)

以及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
  }
}
Run Code Online (Sandbox Code Playgroud)

更通用的解决方案:

    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
     }
}
Run Code Online (Sandbox Code Playgroud)


pas*_*sql 5

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

在此输入图像描述


man*_*ahn 5

使用backgroundView很好,但是它不能像Mail.app中那样很好地滚动。

我做了与xtravar类似的事情。

我在的视图层次结构之外添加了一个视图tableViewController等级制

然后我在下面使用了以下代码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
Run Code Online (Sandbox Code Playgroud)

基本上,我将添加emptyStateViewtableView对象的子视图。由于分隔符将与视图重叠,因此将其颜色设置为clearColor。要恢复默认的分隔符颜色,您可以将其设置为nil


Rep*_*ose 5

多个数据集和部分有一个特定的用例,其中每个部分都需要一个空状态。

您可以使用此问题的多个答案中提到的建议 - 提供自定义空状态单元格。

我将尝试以编程方式更详细地引导您完成所有步骤,希望这会有所帮助。这是我们可以预期的结果:

在此输入图像描述

为了简单起见,我们将使用 2 个数据集(2 个部分),这些数据集是静态的。

我还将假设您的 tableView 逻辑的其余部分与数据集、tabvleView 单元格和部分正常工作。

Swift 5,让我们这样做:

1.创建自定义空状态UITableViewCell类:

class EmptyTableViewCell: UITableViewCell {

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupView()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    let label: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Empty State Message"
        label.font = .systemFont(ofSize: 16)
        label.textColor = .gray
        label.textAlignment = .left
        label.numberOfLines = 1
        return label
    }()

    private func setupView(){
        contentView.addSubviews(label)
        let layoutGuide = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            label.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor),
            label.topAnchor.constraint(equalTo: layoutGuide.topAnchor),
            label.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor),
            label.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor),
            label.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
}
Run Code Online (Sandbox Code Playgroud)

2. 将以下内容添加到 UITableViewController 类中以注册空单元格:

class TableViewController: UITableViewController {
    ...
    let emptyCellReuseIdentifier = "emptyCellReuseIdentifier"
    ...
    override func viewDidLoad(){
        ...
        tableView.register(EmptyTableViewCell.self, forCellReuseIdentifier: emptyCellReuseIdentifier)
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

3. 现在让我们强调一下上面提到的一些假设:

class TableViewController: UITableViewController {
    // 2 Data Sets
    var firstDataSet: [String] = []
    var secondDataSet: [String] = []

    // Sections array
    let sections: [SectionHeader] = [
        .init(id: 0, title: "First Section"),
        .init(id: 1, title: "Second Section")
    ]
    ...
    // MARK: - Table view data source
    override func numberOfSections(in tableView: UITableView) -> Int {
        sections.count
    }
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return sections[section].title
    }
        ...
    }

struct SectionHeader {
    let id: Int
    let title: String
}
Run Code Online (Sandbox Code Playgroud)

4. 现在让我们向数据源添加一些自定义逻辑以处理部分中的空行。如果数据集为空,我们将返回 1 行:

class TableViewController: UITableViewController {
    ...
    // MARK: - Table view data source
    ...
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch section{
        case 0:
            let numberOfRows = firstDataSet.isEmpty ? 1 : firstDataSet.count
            return numberOfRows          
        case 1:
            let numberOfRows = secondDataSet.isEmpty ? 1 : secondDataSet.count
            return numberOfRows   
        default:
            return 0
        }
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

5.最后,最重要的“cellForRowAt indexPath”:

class TableViewController: UITableViewController {
    ...
    // MARK: - Table view data source
    ...
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        // Handle Empty Rows State
        switch indexPath.section {
        case 0:
            if firstDataSet.isEmpty {
                if let cell = tableView.dequeueReusableCell(withIdentifier: emptyCellReuseIdentifier) as? EmptyTableViewCell {
                    cell.label.text = "First Data Set Is Empty"
                    return cell
                }
            }
        case 1:
            if secondDataSet.isEmpty {
                if let cell = tableView.dequeueReusableCell(withIdentifier: emptyCellReuseIdentifier) as? EmptyTableViewCell {
                    cell.label.text = "Second Data Set Is Empty"
                    return cell
                }
            }
        default:
            break
        }
        
        // Handle Existing Data Sets
        if let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath) as? TableViewCell {
            switch indexPath.section {
            case 0:
                ...
            case 1:
                ...
            default:
                break
            }
            return cell
        }

        return UITableViewCell()
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)