A drop-in UITableView/UICollectionView superclass category for showing empty datasets whenever the view has no content to display

Overview

DZNEmptyDataSet

Travis codecov Pod Version Carthage compatible License

Projects using this library

Add your project to the list here and provide a (320px wide) render of the result.

The Empty Data Set Pattern

Also known as Empty State or Blank Slate.

Most applications show lists of content (data sets), which many turn out to be empty at one point, specially for new users with blank accounts. Empty screens create confusion by not being clear about what's going on, if there is an error/bug or if the user is supposed to do something within your app to be able to consume the content.

Please read this very interesting article about Designing For The Empty States.

Screenshots_Row1 Screenshots_Row2 (These are real life examples, available in the 'Applications' sample project in the v2-Swift branch)

Empty Data Sets are helpful for:

  • Avoiding white-screens and communicating to your users why the screen is empty.
  • Calling to action (particularly as an onboarding process).
  • Avoiding other interruptive mechanisms like showing error alerts.
  • Being consistent and improving the user experience.
  • Delivering a brand presence.

Features

  • Compatible with UITableView and UICollectionView. Also compatible with UISearchDisplayController and UIScrollView.
  • Gives multiple possibilities of layout and appearance, by showing an image and/or title label and/or description label and/or button.
  • Uses NSAttributedString for easier appearance customisation.
  • Uses auto-layout to automagically center the content to the tableview, with auto-rotation support. Also accepts custom vertical and horizontal alignment.
  • Background color customisation.
  • Allows tap gesture on the whole tableview rectangle (useful for resigning first responder or similar actions).
  • For more advanced customisation, it allows a custom view.
  • Compatible with Storyboard.
  • Compatible with iOS 6, tvOS 9, or later.
  • Compatible with iPhone, iPad, and Apple TV.
  • App Store ready

This library has been designed in a way where you won't need to extend UITableView or UICollectionView class. It will still work when using UITableViewController or UICollectionViewController. By just conforming to DZNEmptyDataSetSource & DZNEmptyDataSetDelegate, you will be able to fully customize the content and appearance of the empty states for your application.

Installation

Available in CocoaPods

pod 'DZNEmptyDataSet'

To integrate DZNEmptyDataSet into your Xcode project using Carthage, specify it in your Cartfile:

github "dzenbot/DZNEmptyDataSet"

To integrate DZNEmptyDataSet into your Xcode project using SPM, specify it in your Package.swift:

.package(
    url: "https://github.com/dzenbot/DZNEmptyDataSet",
    .branch("master")
)

For existing, go to "File Navigator" -> "Select Project" -> "Project Name" -> "Swift Packages" -> "+" -> Paste "https://github.com/dzenbot/DZNEmptyDataSet" and proceed to select your target.

How to use

For complete documentation, visit CocoaPods' auto-generated doc

Import

#import "UIScrollView+EmptyDataSet.h"

Unless you are importing as a framework, then do:

#import <DZNEmptyDataSet/UIScrollView+EmptyDataSet.h>

Protocol Conformance

Conform to datasource and/or delegate.

@interface MainViewController : UITableViewController <DZNEmptyDataSetSource, DZNEmptyDataSetDelegate>

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.tableView.emptyDataSetSource = self;
    self.tableView.emptyDataSetDelegate = self;
    
    // A little trick for removing the cell separators
    self.tableView.tableFooterView = [UIView new];
}

Data Source Implementation

Return the content you want to show on the empty state, and take advantage of NSAttributedString features to customise the text appearance.

The image for the empty state:

- (UIImage *)imageForEmptyDataSet:(UIScrollView *)scrollView
{
    return [UIImage imageNamed:@"empty_placeholder"];
}

The attributed string for the title of the empty state:

- (NSAttributedString *)titleForEmptyDataSet:(UIScrollView *)scrollView
{
    NSString *text = @"Please Allow Photo Access";
    
    NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:18.0f],
                                 NSForegroundColorAttributeName: [UIColor darkGrayColor]};
    
    return [[NSAttributedString alloc] initWithString:text attributes:attributes];
}

The attributed string for the description of the empty state:

- (NSAttributedString *)descriptionForEmptyDataSet:(UIScrollView *)scrollView
{
    NSString *text = @"This allows you to share photos from your library and save photos to your camera roll.";
    
    NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
    paragraph.lineBreakMode = NSLineBreakByWordWrapping;
    paragraph.alignment = NSTextAlignmentCenter;
    
    NSDictionary *attributes = @{NSFontAttributeName: [UIFont systemFontOfSize:14.0f],
                                 NSForegroundColorAttributeName: [UIColor lightGrayColor],
                                 NSParagraphStyleAttributeName: paragraph};
                                 
    return [[NSAttributedString alloc] initWithString:text attributes:attributes];                      
}

The attributed string to be used for the specified button state:

- (NSAttributedString *)buttonTitleForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state
{
    NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:17.0f]};

    return [[NSAttributedString alloc] initWithString:@"Continue" attributes:attributes];
}

or the image to be used for the specified button state:

- (UIImage *)buttonImageForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state
{
    return [UIImage imageNamed:@"button_image"];
}

The background color for the empty state:

- (UIColor *)backgroundColorForEmptyDataSet:(UIScrollView *)scrollView
{
    return [UIColor whiteColor];
}

If you need a more complex layout, you can return a custom view instead:

- (UIView *)customViewForEmptyDataSet:(UIScrollView *)scrollView
{
    UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    [activityView startAnimating];
    return activityView;
}

The image view animation

- (CAAnimation *)imageAnimationForEmptyDataSet:(UIScrollView *)scrollView
{
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath: @"transform"];
    
    animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
    animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI_2, 0.0, 0.0, 1.0)];
    
    animation.duration = 0.25;
    animation.cumulative = YES;
    animation.repeatCount = MAXFLOAT;
    
    return animation;
}

Additionally, you can also adjust the vertical alignment of the content view (ie: useful when there is tableHeaderView visible):

- (CGFloat)verticalOffsetForEmptyDataSet:(UIScrollView *)scrollView
{
    return -self.tableView.tableHeaderView.frame.size.height/2.0f;
}

Finally, you can separate components from each other (default separation is 11 pts):

- (CGFloat)spaceHeightForEmptyDataSet:(UIScrollView *)scrollView
{
    return 20.0f;
}

Delegate Implementation

Return the behaviours you would expect from the empty states, and receive the user events.

Asks to know if the empty state should be rendered and displayed (Default is YES) :

- (BOOL)emptyDataSetShouldDisplay:(UIScrollView *)scrollView
{
    return YES;
}

Asks for interaction permission (Default is YES) :

- (BOOL)emptyDataSetShouldAllowTouch:(UIScrollView *)scrollView
{
    return YES;
}

Asks for scrolling permission (Default is NO) :

- (BOOL)emptyDataSetShouldAllowScroll:(UIScrollView *)scrollView
{
    return YES;
}

Asks for image view animation permission (Default is NO) :

- (BOOL) emptyDataSetShouldAllowImageViewAnimate:(UIScrollView *)scrollView
{
    return YES;
}

Notifies when the dataset view was tapped:

- (void)emptyDataSet:(UIScrollView *)scrollView didTapView:(UIView *)view
{
    // Do something
}

Notifies when the data set call to action button was tapped:

- (void)emptyDataSet:(UIScrollView *)scrollView didTapButton:(UIButton *)button
{
    // Do something
}

Refresh layout

If you need to refresh the empty state layout, simply call:

[self.tableView reloadData];

or

[self.collectionView reloadData];

depending of which you are using.

Force layout update

You can also call [self.tableView reloadEmptyDataSet] to invalidate the current empty state layout and trigger a layout update, bypassing -reloadData. This might be useful if you have a lot of logic on your data source that you want to avoid calling, when not needed. [self.scrollView reloadEmptyDataSet] is the only way to refresh content when using with UIScrollView.

Sample projects

Applications

This project replicates several popular application's empty states (~20) with their exact content and appearance, such as Airbnb, Dropbox, Facebook, Foursquare, and many others. See how easy and flexible it is to customize the appearance of your empty states. You can also use this project as a playground to test things.

Countries

This project shows a list of the world countries loaded from CoreData. It uses NSFecthedResultController for filtering search. When searching and no content is matched, a simple empty state is shown. See how to interact between the UITableViewDataSource and the DZNEmptyDataSetSource protocols, while using a typical CoreData stack.

Colors

This project is a simple example of how this library also works with UICollectionView and UISearchDisplayController, while using Storyboards.

Collaboration

Feel free to collaborate with ideas, issues and/or pull requests.

License

(The MIT License)

Copyright (c) 2016 Ignacio Romero Zurbuchen [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Comments
  • Empty data set in different positions

    Empty data set in different positions

    Hi,

    I'm having a very strange problem: the empty data set moves to a different position after some data have been available for a controller and then were removed.

    The screenshot below shows what I have in mind:

    dzn-error

    The empty data set first appears as on the left screenshot. Then, after adding and deleting some data it appears as on the right screenshot.

    I do reload the view in viewWillAppear.

    This is only a minor issue but do you have any ideas on why this might be happening?

    Cheers,

    Pawel

    opened by pawelkata 40
  • Allowing touches to views underneath the empty view.

    Allowing touches to views underneath the empty view.

    Since userInteractionEnabled is set to YES on emptyDataSetView, all touches to views underneath are blocked.

    This is probably fine for the majority of cases, however I have a header in my UICollectionView using a supplementaryView. Even though the main content is empty, there are still actions that need to be performed from the header. Touches to the supplementary view are currently blocked making it unusable.

    It would be great to have an option to enable the behaviour I described above. This is actually what I expected -emptyDataSetShouldAllowTouch: to do, but that is not the case.

    opened by brandons 27
  • EXC_BAD_ACCESS on dzn_canDisplay

    EXC_BAD_ACCESS on dzn_canDisplay

    I'm sometimes getting an EXC_BAD_ACCESS crash in the first if in this code in UIScrollView+EmptyDataSet.m

    - (BOOL)dzn_canDisplay
    {
        if (self.emptyDataSetSource && [self.emptyDataSetSource conformsToProtocol:@protocol(DZNEmptyDataSetSource)]) {
            if ([self isKindOfClass:[UITableView class]] || [self isKindOfClass:[UICollectionView class]] || [self isKindOfClass:[UIScrollView class]]) {
                return YES;
            }
        }
    
        return NO;
    }
    
    opened by lm2s 21
  • Buttons not registering tap events

    Buttons not registering tap events

    The buttons returned (shown) from

    - (NSAttributedString *)buttonTitleForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state;

    aren't registering touch events. The button appears to be displayed properly but tapping them does nothing on devices running iOS 7.x using Xcode 5.1.1. However, the buttons are working properly in the iOS 8 betas.

    Thanks, Wes

    opened by Dysonapps 20
  • weird issue with uicollectionview

    weird issue with uicollectionview

    Hello and thanks for this excellent library. i am facing a weird issue. In a UITableView environment when a user taps a uitextfield, inside a UITableViewCell, i am showing a uicollectionview as a customInputAccessoryView that shows words based on user input. when the user taps the texfield the app now freezes and memory goes well over 1GB. There must be a code loop thats happening in reloadData i am using this as a inputAccesoryView which is basically a custom uicollectionview that shows words inside horizontal cells. https://github.com/siuying/IGAutoCompletionToolbar

    Can you provide any assistance on why this freezes? Is there a way to disable the swizzling for specific views?

    thanks Konstantinos

    opened by mythodeia 20
  • Deallocation of TableViewController causes crash

    Deallocation of TableViewController causes crash

    When using the new release 1.6 then this happens when deallocation the TVC using DZNEmptyDataSet

    2015-07-13 16:58:43.683 -[UITableView release]: message sent to deallocated instance

    This happens even if the data set source and the delegate is set to nil.

    -(void)dealloc { DLog(@"Dealloc %@ ",[self class]); self.tableView.emptyDataSetSource = nil; self.tableView.emptyDataSetDelegate = nil; }

    opened by tbechtum 19
  • UIScrollView+EmptyDataSet breaks default scrolling behavior

    UIScrollView+EmptyDataSet breaks default scrolling behavior

    Hi, I just encountered this odd bug, which caused me couple days to figure out. My app has a nested scrollView layout. Something looks like the nested layout in this article: http://ashfurrow.com/blog/putting-a-uicollectionview-in-a-uitableviewcell/ For iOS 5 and later, a gesture will lock to a scroll view once it is triggered, which means I can either scroll vertically OR horizontally, but not both. However, if you import UIScrollView+EmptyDataSet to the project above, it will allow simultaneous scrolling. Any idea how to fix it?

    opened by liweihan 19
  • Empty set view never shown on load with NSFetchedResultsController

    Empty set view never shown on load with NSFetchedResultsController

    I've been having an issue where the empty set view is never shown when the app opens and there is no data in my FetchedResultsController. The view shows correctly if I open the app, inject some data into the data store and then manually delete all the rows.

    The issue appears to be in the - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context where both new and old never differ because the tableView never adds any rows.

    I'm not sure if this is a function of the fact that I'm using a fetchedResultsController to load my data from the store or if this is an issue that happens all the time (I imagine it's the former).

    I've managed to hack around this for the time being by changing:

            if (new && old && ![new isEqualToValue:old]) {
                if ([keyPath isEqualToString:kContentSize]) {
                    [self didReloadData];
                }
            }
    

    to

            if (new && old && ![new isEqualToValue:old]) {
                if ([keyPath isEqualToString:kContentSize]) {
                    [self didReloadData];
                }
            } else {
                [self reloadDataSet];
            }
    

    but I'm sure this isn't the correct or best way to do things here. Is this the way it is intended to work? I can provide a more in depth example if needed.

    opened by samturner 19
  • Submit your renders here (320px wide please)

    Submit your renders here (320px wide please)

    If you are using DZNEmptyDataSet in your application, please take a screenshot of your empty dataset render and submit it here. This will help others to imagine all possibilities with the library. Thank you!

    PS: 320px wide please

    opened by dzenbot 17
  • UILabel Size

    UILabel Size

    For some reason the height of my description label is much larger than it should be. In fact, this seems to be an issue for the bottom most element, as when I add a button title, the button height is abnormally tall as well. Any idea what might be causing this?

    screen shot 2015-12-22 at 12 46 28 pm

    bug 
    opened by im-jersh 14
  • Alignment Issue

    Alignment Issue

    Hi, this is just a minor issue but I guess you may want to acknowledge about it.

    The vertical alignment of titleForEmptyDataSet, descriptionForEmptyDataSet and imageForEmptyDataSet appears differently in these scenarios:

    1and2

    1. The app is first launched
    2. How it looks after 1 row was added and deleted

    3and4 3 & 4. When moves to another screen (Show segue) and goes back.

    Here is the overview of them: all

    I hope you already have the solution for this.

    opened by dkngyn 13
  • Simplify calls and add scenario concepts

    Simplify calls and add scenario concepts

    1. The concept of scene is added. Each scene corresponds to an empty data set. Users can freely switch the display of empty data sets according to the scene to simplify the code logic.
    2. The use of chained call mode simplifies the data configuration of empty data sets and greatly reduces the amount of code.
    3. Replace the proxy with block, and the code is more cohesive.
    截屏2022-07-05 下午3 59 01 截屏2022-07-05 下午3 59 12 截屏2022-07-05 下午3 59 23
    opened by jlzeng1991 1
  • DetailLabel and TitleLabel layout is NOT set well

    DetailLabel and TitleLabel layout is NOT set well

    For the setting constraints of detailLabel or titleLabel, ` if ([self canShowDetail]) {

            [subviewStrings addObject:@"detailLabel"];
            views[[subviewStrings lastObject]] = _detailLabel;
            
            [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-([email protected])-[detailLabel(>=0)]-([email protected])-|"
                                                                                     options:0 metrics:metrics views:views]];
        }`
    

    the leading and trailing constraints priority are not .default as 1000, which make our label will be exceed the superView. Could we set it to 1000 from 750 ?

    opened by PCBZ 1
  • App crashed with unrecognized selector  setClaCustomViewVisible

    App crashed with unrecognized selector setClaCustomViewVisible

    [DZNEmptyDataSetView setClaCustomViewVisible:]: unrecognized selector;I search all over my project, no method with name related to setClaCustomViewVisible or CustomViewVisible. To avoid the crash, I edited the source code ,implement the method setClaCustomViewVisible

    opened by Antonikovsky 0
Owner
Ignacio Romero Zurbuchen
iOS Dev. Designer. Sometimes I contribute to open source.
Ignacio Romero Zurbuchen
Nice library to show placeholders and Empty States for any UITableView/UICollectionView in your project

HGPlaceholders Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements iOS 8.0+ Xcode 9

Hamza Ghazouani 2.1k Sep 15, 2022
An iOS drop-in UITableView, UICollectionView and UIScrollView superclass category for showing a customizable floating button on top of it.

MEVFloatingButton An iOS drop-in UITableView, UICollectionView, UIScrollView superclass category for showing a customizable floating button on top of

Manuel Escrig 298 Jul 17, 2022
WLEmptyState is an iOS based component that lets you customize the view when the dataset of a UITableView or a UICollectionView is empty.

Table of Content Overview Running an Example Project Installing WLEmptyState Configuring WLEmptyState Using WLEmptyState Customizing WLEmptyState Cont

Wizeline 314 Sep 14, 2022
Nice library to show placeholders and Empty States for any UITableView/UICollectionView in your project

HGPlaceholders Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements iOS 8.0+ Xcode 9

Hamza Ghazouani 2.1k Sep 15, 2022
Automates prefetching of content in UITableView and UICollectionView

Automates preheating (prefetching) of content in UITableView and UICollectionView. Deprecated on iOS 10. This library is similar to UITableViewDataSou

Alexander Grebenyuk 633 Sep 16, 2022
Showing / dismissing keyboard animation in simple UIViewController category.

RSKKeyboardAnimationObserver Easy way to handle iOS keyboard showing/dismissing. Introduction Working with iOS keyboard demands a lot of duplicated co

Ruslan Skorb 45 Jun 9, 2022
An adaptive scrollable graph view for iOS to visualise simple discrete datasets. Written in Swift.

ScrollableGraphView Announcements 9-7-2017 - Version 4: Version 4 was released which adds multiple plots, dynamic reloading of values, more reference

Phillip 5.3k Sep 20, 2022
Placeholder views based on content, loading, error or empty states

StatefulViewController A protocol to enable UIViewControllers or UIViews to present placeholder views based on content, loading, error or empty states

Alexander Schuch 2.1k Sep 4, 2022
PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a consistent user interface for common content states (i.e. loading, loaded, empty, and error).

PJFDataSource PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a co

Square 88 Jun 30, 2022
Placeholder views based on content, loading, error or empty states

StatefulViewController A protocol to enable UIViewControllers or UIViews to present placeholder views based on content, loading, error or empty states

Alexander Schuch 2.1k Sep 4, 2022
slider view for choosing categories. add any UIView type as category item view. Fully customisable

CategorySliderView Horizontal or vertical slider view for choosing categories. Add any UIView type as category item view. Fully customisable Demo Inst

Cem Olcay 354 Mar 22, 2022
Media view which subclasses UIImageView, and can display & load images, videos, GIFs, and audio and from the web, and has functionality to minimize from fullscreen, as well as show GIF previews for videos.

I've built out the Swift version of this library! Screenshots Description ABMediaView can display images, videos, as well as now GIFs and Audio! It su

Andrew Boryk 79 Aug 29, 2022
HoverConversion realized vertical paging with UITableView. UIViewController will be paged when reaching top or bottom of UITableView contentOffset.

HoverConversion ManiacDev.com referred. https://maniacdev.com/2016/09/hoverconversion-a-swift-ui-component-for-navigating-between-multiple-table-views

Taiki Suzuki 166 Feb 1, 2022
Carbon🚴 A declarative library for building component-based user interfaces in UITableView and UICollectionView.

A declarative library for building component-based user interfaces in UITableView and UICollectionView. Declarative Component-Based Non-Destructive Pr

Ryo Aoyama 1.2k Sep 15, 2022
A UICollectionViewLayout subclass displays its items as rows of items similar to the App Store Feature tab without a nested UITableView/UICollectionView hack.

CollectionViewShelfLayout A UICollectionViewLayout subclass displays its items as rows of items similar to the App Store Feature tab without a nested

Pitiphong Phongpattranont 372 Jun 29, 2022
Animated top menu for UITableView / UICollectionView / UIScrollView written in Swift

Persei Animated top menu for UITableView / UICollectionView / UIScrollView written in Swift! Made in Yalantis. Check this project on Dribbble Check th

Yalantis 3.4k Sep 23, 2022
Netflix and App Store like UITableView with UICollectionView, written in pure Swift 4.2

GLTableCollectionView Branch Status master develop What it is GLTableCollectionView is a ready to use UITableViewController with a UICollectionView fo

Giulio 710 Sep 1, 2022
Incremental update tool to UITableView and UICollectionView

EditDistance is one of the incremental update tool for UITableView and UICollectionView. The followings show how this library update UI. They generate

Kazuhiro Hayashi 90 Jun 9, 2022
A generic small reusable components for data source implementation for UITableView/UICollectionView in Swift.

GenericDataSource A generic small reusable components for data source implementation for UITableView/UICollectionView written in Swift. Features Basic

null 132 Sep 8, 2021