Simple bindable "no data" placeholder for UITableView
Almost every iOS application uses UITableView
to display some kind of data and in many cases the data can be missing. In such situation you typically want to display some kind of “no data” placeholder. The placeholder fills in the empty space and can tell the user what needs to be done to get the data.
There are many ways wo implement such “no data” placeholder, including using libraries like UIEmptyState. If you just need to display a simple text message, there is an really easy way to implement it yourself, as I did in a recent project.
Showing and hiding the placeholder
The easiest way to create a “no data” placeholder for UITableView
is to set it as the background view. It will be shown when there is no data in the UITableView
so it will not be hidden under any other wiew.
I created a simple UITableView
extension to show the placeholder with a specific message
extension UITableView {
func setNoDataPlaceholder(_ message: String) {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height))
label.text = message
// styling
label.sizeToFit()
self.isScrollEnabled = false
self.backgroundView = label
self.separatorStyle = .none
}
}
To hide it when no longer needed I created another extension method
extension UITableView {
func removeNoDataPlaceholder() {
self.isScrollEnabled = true
self.backgroundView = nil
self.separatorStyle = .singleLine
}
}
With this I would have to call those two extension method manually depending on the data in the UITableView
which is unnecessary manual work.
Making the placeholder bindable
I use RxSwift
in the project so I made it bindable.
extension Reactive where Base: UITableView {
func isEmpty(message: String) -> Binder<Bool> {
return Binder(base) { tableView, isEmpty in
if isEmpty {
tableView.setNoDataPlaceholder(message)
} else {
tableView.removeNoDataPlaceholder()
}
}
}
}
This extension adds an isEmpty
function to every UITableView
that you can call with the desired “no data” message and get back a property that you can use for binding
let isEmpty = tableView.rx.isEmpty(message: L10n.noResponses)
viewModel.responses.map({ $0.count <= 0 }).distinctUntilChanged().bind(to: isEmpty).disposed(by: disposeBag)
The L10n.noResponses
is just a safer way to use translated strings and viewModel.responses
is a and observable backing the UITableView
data.
With this binding you can be sure the placeholder is only shown when needed, without any manual calls to the two extension methods.