Answers:
UITableView的backgroundView属性是您的朋友。
您应该在其中viewDidLoad
或任何地方reloadData
确定表是否为空,并使用包含UILabel的UIView更新UITableView的backgroundView属性,或者将其设置为nil。而已。
当然,可以使UITableView的数据源承担双重职责,并返回一个特殊的“列表为空”单元格,这让我大吃一惊。突然numberOfRowsInSection:(NSInteger)section
不得不计算其他部分的行数,以确保它们也为空。您还需要制作一个包含空消息的特殊单元格。同样不要忘记,您可能需要更改单元格的高度以容纳空消息。这都是可行的,但似乎在创可贴之上是创可贴。
backgroundView
隐藏属性是必经之路。使用DZNEmptyDataSet
,我们必须使用它的emptyDataSetSource
tableView.backgroundView!.userInteraction = true
在该行之后设置tableView.backgroundView = constructMyViewWithButtons()
,或者将其设置。
与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
}
}
用法:
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
}
func numberOfSections(in tableView: UITableView) -> Int
方法
根据这里的答案,这是我制作的一个快速类,您可以在中使用它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
}
}
在http://www.appcoda.com/pull-to-refresh-uitableview-empty/的帮助下
viewController.tableView.separatorStyle = .none
不需要。
我建议使用以下库: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()
现在,要显示空状态,您需要做的就是:
//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 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)
}
一种实现方法是修改数据源以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
已配置了相应消息的实例设置它。
deleteRowsAtIndexPaths:withRowAnimation:
因为从行返回的数字numberOfRowsInSection
需要与$ numRows-$ rowsDeleted的结果相匹配。
我一直在使用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;
}
return tableView.numberOfRowsInSection(section) == 0 ? "This list is now empty" : nil
因此,为了获得更安全的解决方案:
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
}
}
更通用的解决方案:
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
}
}
使用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
基本上,我将添加emptyStateView
为tableView
对象的子视图。由于分隔符将与视图重叠,因此将其颜色设置为clearColor
。要恢复默认的分隔符颜色,您可以将其设置为nil
。
根据Apple的说法,使用Container View Controller是正确的方法。
我将所有空状态视图放在单独的情节提要中。每个对象都有自己的UIViewController子类。我直接在其根视图下添加内容。如果需要任何操作/按钮,则现在已经有一个控制器来处理它。
然后,只需从该Storyboard实例化所需的视图控制器,将其添加为子视图控制器,然后将容器视图添加到tableView的层次结构(子视图)即可。空状态视图也将是可滚动的,这感觉很好,并允许您实现拉动以刷新。
阅读“将子视图控制器添加到您的内容”一章,以获取有关如何实现的帮助。
只需确保将子视图框架设置为
(0, 0, tableView.frame.width, tableView.frame.height)
,即可使所有内容居中并正确对齐。
首先,其他流行方法存在的问题。
背景视图
如果要使用将其设置为UILabel的简单情况,则背景视图不会很好地居中。
单元格,页眉或页脚以显示消息
这会干扰您的功能代码并引入奇怪的情况。如果您想完美地使您的信息居中,那将增加另一个层次的复杂性。
滚动自己的表视图控制器
您将失去诸如refreshControl之类的内置功能,然后重新发明轮子。坚持使用UITableViewController以获得最佳的可维护结果。
将UITableViewController添加为子视图控制器
我有种感觉,您最终会遇到iOS 7+中的contentInset问题-还有为什么使事情变得复杂?
我的解决方案
我想出的最佳解决方案(当然,这并不理想)是制作一个特殊的视图,该视图可以位于滚动视图的顶部并采取相应的措施。这在带有contentInset疯狂的iOS 7中显然变得复杂,但这是可行的。
您必须注意的事项:
一旦在一个UIView子类中弄清楚了这一点,就可以将其用于所有内容-加载微调器,禁用视图,显示错误消息等。
addSubView
它,它会附加在表格视图上,这总是会导致自动布局出现问题。
这是最好,最简单的解决方案。
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;
显示消息为空列表,无论是UITableView还是UICollectionView。
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
}
}
}
用途:
if cellsViewModels.count == 0 {
self.tableView.showEmptyListMessage("No Product In List!")
}
要么:
if cellsViewModels.count == 0 {
self.collectionView?.showEmptyListMessage("No Product In List!")
}
切记: 不要忘记删除消息标签,以防刷新后出现数据。
使用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
}
您可以将此添加到您的基类。
var messageLabel = UILabel()
func showNoDataMessage(msg: String) {
let rect = CGRect(origin: CGPoint(x: 0, y :self.view.center.y), size: CGSize(width: self.view.bounds.width - 16, height: 50.0))
messageLabel = UILabel(frame: rect)
messageLabel.center = self.view.center
messageLabel.text = msg
messageLabel.numberOfLines = 0
messageLabel.textColor = Colors.grayText
messageLabel.textAlignment = .center;
messageLabel.font = UIFont(name: "Lato-Regular", size: 17)
self.view.addSubview(messageLabel)
self.view.bringSubviewToFront(messageLabel)
}
在从api获取数据的类中将其显示为此类。
func populateData(dataSource : [PRNJobDataSource]){
self.dataSource = dataSource
self.tblView.reloadData()
if self.dataSource.count == 0 {self.showNoDataMessage(msg: "No data found.")}
}
这样隐藏它。
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.dataSource.count > 0 {self.hideNoDataMessage()}
return dataSource.count
}
func hideNoDataMessage(){
messageLabel.removeFromSuperview()
}
迅捷版,但形式更好,更简单。** 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;
}
可能不是最大的解决方案,但我只是在表的底部放置了一个标签,如果行= 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 = ""
}
最简单,最快的方法是将标签拖到tableView下的侧面板上。为标签和tableView创建出口,并添加if语句以根据需要隐藏和显示标签和表格。或者,您可以将tableView.tableFooterView = UIView(frame:CGRect.zero)添加到您的viewDidLoad()中,以使空表感知到如果表和背景视图具有相同的颜色,则该表是隐藏的。
我进行了一些更改,以便我们无需手动检查计数,还为标签添加了约束,以便无论消息有多大,都不会出错,如下所示:
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
}
}
用法
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let message: String = "The list is empty."
ticketsTableView.setEmptyMessage(message, tickets.isEmpty)
return self.tickets.count
}
或者,您可以使用一些可定制的轻量级库