Skip to content
This repository has been archived by the owner on Jun 30, 2021. It is now read-only.

Commit

Permalink
Version 1.0.0 - Stable Release
Browse files Browse the repository at this point in the history
- Add Swift version check to allow support for Swift 3 --> Swift 4.
- Refactor public API to make it less verbose and more Swift-like.
- Add two new delegate methods to the `UIEmptyStateDelegate`
- Fix some broken documentation/updated docs
  • Loading branch information
Luis Padron committed Jul 14, 2017
1 parent bc81436 commit ca67239
Show file tree
Hide file tree
Showing 34 changed files with 762 additions and 615 deletions.
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
# UIEmptyState Changelog
# UIEmptyState CHANGELOG

## Version 1.0.0 - Stable Release

- Add Swift version check to allow support for Swift 3 --> Swift 4.
- Refactor public API to make it less verbose and more Swift-like.
* All methods which had the format `methodName(forSomething:)` have been refactored to simply `methodName(for:)`.
* Due to this renaming, if using Swift 3.2 or lower, you may get an error
about a `@objc` method having already been declared, this is due to Swift 3 inferring an `@objc` attribute when it is not in fact `@objc`. If using Xcode 9 +, you will need to set `Swift 3 @objc Inference` in the `Optimization Level` of this projects `Build Settings` to `Off`. I know this is a hassle, but I want to keep the API clean and stable, no point in changing it at a later date when Swift 4 is fully released and breaking more code.
- Add two new delegate methods to the `UIEmptyStateDelegate`
* `emptyStateViewWillShow(view: UIView)` which is called before the view is shown, given you time to do any additional work.
* `emptyStateViewWillHide(view: UIView)` which is called right before the view will be hidden from the screen.
- Fix some broken documentation/updated docs

After this release the API should not change that often, thus I wont be breaking your code as much 😅

Thanks for using `UIEmptyState`


## Version 0.8.3

Expand Down
51 changes: 0 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,64 +97,13 @@ If you need more help take a look at the example project here (Pokemon nerds, wi

## Documentation

Quick overview of available `UIEmptyStateDataSource` properties

```swift
///////////// METHODS /////////////
// If empty view should show, implemented by default
func shouldShowEmptyStateView(forTableView:) -> Bool
// If empty view should show, implemented by default
func shouldShowEmptyStateView(forCollectionView:) -> Bool
// The block for the animation code, basic animation by default
func emptyStateViewAnimation(forView,animationDuration:completion) -> Bool

///////////// COMPUTED PROPERTIES /////////////
// The view to show, implemented by default
var emptyStateView: UIView
// Whether the view adjusts and resizes to fit and be centered when inside a nav controller
var emptyStateViewAdjustsToFitBars: Bool
// The text for the title view, implemented by default
var emptyStateTitle: NSAttributedString
// The image for the image view, nil by default
var emptyStateImage: UIImage?
// The size of the image view, nil by default
var emptyStateImageSize: CGSize?
// The text for the button title, nil by default
var emptyStateButtonTitle: NSAttributedString?
// The image for the button, nil by default
var emptyStateButtonImage: UIImage?
// The size of the button, nil by default
var emptyStateButtonSize: CGSize?
// The detail message for the view, nil by default
var emptyStateDetailMessage: NSAttributedString?
// The spacing inbetween views, 12 by default
var emptyStateViewSpacing: CGFloat
// The background color for the view, UIColor.clear by default
var emptyStateBackgroundColor: UIColor
// Whether view can scroll when showing, false by default
var emptyStateViewCanScroll: Bool
// Whether view can animate, true by default
var emptyStateViewCanAnimate: Bool
// Whether view animates everytime it appears, true by default
var emptyStateViewAnimatesEverytime: Bool
// The animation duration for the view animation, 0.5 by default
var emptyStateViewAnimationDuration: TimeInterval
```

#### [Read the full documentation here](http://htmlpreview.github.io/?https://github.com/luispadron/UIEmptyState/blob/master/docs/index.html)

## Example Project

#### Clone this repo and run the `UIEmptyStateExample` project

## Roadmap
- [x] Add support for any `UIViewController` subclass, i.e `UICollectionView` etc.
- [ ] Figure out nicer method for reloading emptystate with out explicitly calling for a reload, maybe method swizzling
- [x] Add animation to view appearance
- [ ] Add nicer animation to button taps, or view taps
- [ ] Add tests
- [ ] Clean up and continue to work on `UIEmptyStateView`, i.e add better constraints and more customization options


## License (MIT)

Expand Down
2 changes: 1 addition & 1 deletion UIEmptyState.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Pod::Spec.new do |s|

s.name = "UIEmptyState"
s.version = "0.8.3"
s.version = "1.0.0"
s.summary = "An empty state control to give visually appealing context when building iOS applications."
s.description = <<-DESC
Empty state control which gives context with either a message, image, and buttons to the user when ever there is a reason the state is empty.
Expand Down
12 changes: 6 additions & 6 deletions UIEmptyState/UIEmptyStateDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public protocol UIEmptyStateDataSource: class {
- returns:
Boolean value of whether view should or should not be displayed
*/
func shouldShowEmptyStateView(forTableView tableView: UITableView) -> Bool
func shouldShowEmptyStateView(for tableView: UITableView) -> Bool

/**
Determines whether should or should not show the empty view for a specific collectionView,
Expand All @@ -33,7 +33,7 @@ public protocol UIEmptyStateDataSource: class {
- returns:
Boolean value of whether view should or should not be displayed
*/
func shouldShowEmptyStateView(forCollectionView collectionView: UICollectionView) -> Bool
func shouldShowEmptyStateView(for collectionView: UICollectionView) -> Bool

/**
Determines the view to use for the empty state, by default this is a nice stack view
Expand Down Expand Up @@ -147,7 +147,7 @@ public protocol UIEmptyStateDataSource: class {
if implementing this pass this to the `UIView.animate` completion block
in order for the delegate to work properly
*/
func emptyStateViewAnimation(forView view: UIView, animationDuration: TimeInterval,
func emptyStateViewAnimation(for view: UIView, animationDuration: TimeInterval,
completion: ((Bool) -> Void)?) -> Void
}

Expand All @@ -158,7 +158,7 @@ extension UIEmptyStateDataSource where Self: UIViewController {
Default implementation for UIViewController tableView determining if should show the emptystate view,
counts number of rows in the tableView
*/
public func shouldShowEmptyStateView(forTableView tableView: UITableView) -> Bool {
public func shouldShowEmptyStateView(for tableView: UITableView) -> Bool {
let sections = tableView.numberOfSections
var rows = 0
for section in 0..<sections {
Expand All @@ -171,7 +171,7 @@ extension UIEmptyStateDataSource where Self: UIViewController {
Default implementation for UIViewController collectionView determining if should show the emptystate view,
counts number of items in the collectionView
*/
public func shouldShowEmptyStateView(forCollectionView collectionView: UICollectionView) -> Bool {
public func shouldShowEmptyStateView(for collectionView: UICollectionView) -> Bool {
let sections = collectionView.numberOfSections
var items = 0
for section in 0..<sections {
Expand Down Expand Up @@ -243,7 +243,7 @@ extension UIEmptyStateDataSource where Self: UIViewController {
public var emptyStateViewAnimationDuration: TimeInterval { get { return 0.5 } }

/// Default implementation of `emptyStateViewAnimation`, implements a simple animation
public func emptyStateViewAnimation(forView view: UIView, animationDuration: TimeInterval,
public func emptyStateViewAnimation(for view: UIView, animationDuration: TimeInterval,
completion: ((Bool) -> Void)?) -> Void {
guard let v = view as? UIEmptyStateView else { return }
// Set initial alpha
Expand Down
24 changes: 22 additions & 2 deletions UIEmptyState/UIEmptyStateDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
you must handle how this delegate operates
*/
public protocol UIEmptyStateDelegate: class {
/**
The call back for when the `emptyStateView` will be shown on screen

- parameters:
- view: The view that is will show
*/
func emptyStateViewWillShow(view: UIView)

/**
The call back for when the `emptyStateView` is now shown on screen

Expand All @@ -23,6 +31,14 @@ public protocol UIEmptyStateDelegate: class {
*/
func emptyStateViewDidShow(view: UIView)

/**
The call back for when the `emptyStateView` will be hidden

- parameters:
- view: The view that will be hidden
*/
func emptyStateViewWillHide(view: UIView)

/**
The call back for when the button inside the emptyStateView is tapped

Expand All @@ -45,17 +61,21 @@ public protocol UIEmptyStateDelegate: class {
- view: The view which finished animating
- didFinish: Whether the animation finished completely, i.e not interrupted
*/
func emptyStateViewAnimationCompleted(forEmptyStateView view: UIView, didFinish: Bool)
func emptyStateViewAnimationCompleted(for view: UIView, didFinish: Bool)
}

/// Extension to add default conformance to UIViewController, by default the method bodies are empty
extension UIEmptyStateDelegate where Self: UIViewController {
/// Default empty implementation of `emptyStateViewWillShow`
public func emptyStateViewWillShow(view: UIView) { }
/// Default empty implementation of `emptyStateViewDidShow`
public func emptyStateViewDidShow(view: UIView) { }
/// Default empty implementation of `emptyStateViewWillHide`
func emptyStateViewWillHide(view: UIView) { }
/// Default empty implementation of `emptyStateButtonWasTapped`
public func emptyStatebuttonWasTapped(button: UIButton) { }
/// Default empty implementation of `emptyStateViewWasTapped`
public func emptyStateViewWasTapped(view: UIView) { }
/// Default empty implementation of `emptyStateViewAnimationCompleted`
public func emptyStateViewAnimationCompleted(forEmptyStateView view: UIView, didFinish: Bool) { }
public func emptyStateViewAnimationCompleted(for view: UIView, didFinish: Bool) { }
}
12 changes: 7 additions & 5 deletions UIEmptyState/UIEmptyStateView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ private extension UILabel {

let maxSize = CGSize(width: width, height: .greatestFiniteMagnitude)

let attrString = NSAttributedString(string: txt,
attributes: [.font : self.font])
let expectedRect = attrString.boundingRect(with: maxSize,
options: .usesLineFragmentOrigin,
context: nil)
#if swift(>=4.0)
let attrString = NSAttributedString(string: txt, attributes: [.font: self.font])
#else
let attrString = NSAttributedString(string: txt, attributes: [NSFontAttributeName: self.font])
#endif

let expectedRect = attrString.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, context: nil)
return ceil(expectedRect.size.height)

}
Expand Down
65 changes: 38 additions & 27 deletions UIEmptyState/UIViewController+UIEmptyState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,14 @@ extension UIViewController {
**Important:**
This should be called whenever changes are made to the tableView data source or after reloading the tableview

DO NOT override this method/implement it unless you need custom behavior.
Do NOT override this method/implement it unless you need custom behavior and know what you are doing.
*/
public func reloadEmptyState(forTableView tableView: UITableView) {
guard let source = emptyStateDataSource, source.shouldShowEmptyStateView(forTableView: tableView) else {
public func reloadEmptyState(for tableView: UITableView) {
guard let source = emptyStateDataSource, source.shouldShowEmptyStateView(for: tableView) else {
// Call the will hide delegate
if let view = emptyStateView {
self.emptyStateDelegate?.emptyStateViewWillHide(view: view)
}
// If shouldnt show view remove from superview, enable scrolling again
emptyStateView?.isHidden = true
tableView.isScrollEnabled = true
Expand All @@ -87,12 +91,17 @@ extension UIViewController {
The method responsible for show and hiding the `UIEmptyStateDataSource.viewForEmptyState` view

**Important:**
This should be called whenever changes are made to the collectionView data source
or after reloading the tableview
This should be called whenever changes are made to the collection view data source or after reloading the collection view.

Do NOT override this method/implement it unless you need custom behavior and know what you are doing.
*/
public func reloadEmptyState(forCollectionView collectionView: UICollectionView) {
public func reloadEmptyState(for collectionView: UICollectionView) {
guard let source = emptyStateDataSource,
source.shouldShowEmptyStateView(forCollectionView: collectionView) else {
source.shouldShowEmptyStateView(for: collectionView) else {
// Call the will hide delegate
if let view = emptyStateView {
self.emptyStateDelegate?.emptyStateViewWillHide(view: view)
}
// If shouldnt show view remove from superview, enable scrolling again
emptyStateView?.isHidden = true
collectionView.isScrollEnabled = true
Expand All @@ -112,7 +121,6 @@ extension UIViewController {

/// Finishes the reload, i.e assigns the empty view, and adjusts any other UI
private func finishReload(for source: UIEmptyStateDataSource) {

let emptyView = showView(for: source)

// Set constraints
Expand Down Expand Up @@ -157,10 +165,13 @@ extension UIViewController {

/// Private helper method which will create the empty state view if not created, or show it if hidden
private func showView(for source: UIEmptyStateDataSource) -> UIView {

if let createdView = emptyStateView {
// Call the will show delegate
self.emptyStateDelegate?.emptyStateViewWillShow(view: createdView)
// View has been created, update it and then reshow
createdView.isHidden = false
guard let view = createdView as? UIEmptyStateView else { return createdView}
guard let view = createdView as? UIEmptyStateView else { return createdView }

view.backgroundColor = source.emptyStateBackgroundColor
view.title = source.emptyStateTitle
Expand All @@ -176,14 +187,13 @@ extension UIViewController {
// Animate now
if source.emptyStateViewCanAnimate && source.emptyStateViewAnimatesEverytime {
DispatchQueue.main.async {
source.emptyStateViewAnimation(forView: view,
animationDuration: source.emptyStateViewAnimationDuration,
completion:
{ finished in

self.emptyStateDelegate?.emptyStateViewAnimationCompleted(forEmptyStateView: view,
didFinish: finished)
})
source.emptyStateViewAnimation(
for: view,
animationDuration: source.emptyStateViewAnimationDuration,
completion: { finished in
self.emptyStateDelegate?.emptyStateViewAnimationCompleted(for: view, didFinish: finished)
}
)
}
}

Expand All @@ -192,6 +202,8 @@ extension UIViewController {
} else {
// We can create the view now
let newView = source.emptyStateView
// Call the will show delegate
self.emptyStateDelegate?.emptyStateViewWillShow(view: newView)
// Add to emptyStateView property
emptyStateView = newView
// Add as a subView, bring it infront of the tableView
Expand All @@ -200,14 +212,13 @@ extension UIViewController {
// Animate now
if source.emptyStateViewCanAnimate {
DispatchQueue.main.async {
source.emptyStateViewAnimation(forView: newView,
animationDuration: source.emptyStateViewAnimationDuration,
completion:
{ finished in

self.emptyStateDelegate?.emptyStateViewAnimationCompleted(forEmptyStateView: newView,
didFinish: finished)
})
source.emptyStateViewAnimation(
for: newView,
animationDuration: source.emptyStateViewAnimationDuration,
completion: { finished in
self.emptyStateDelegate?.emptyStateViewAnimationCompleted(for: newView, didFinish: finished)
}
)
}
}

Expand All @@ -221,7 +232,7 @@ extension UIViewController {
extension UITableViewController {
/// Reloads the empty state, defaults the tableView to `self.tableView`
public func reloadEmptyState() {
self.reloadEmptyState(forTableView: self.tableView)
self.reloadEmptyState(for: self.tableView)
}
}

Expand All @@ -234,7 +245,7 @@ extension UICollectionViewController {
return
}

self.reloadEmptyState(forCollectionView: collectionView)
self.reloadEmptyState(for: collectionView)
}
}

Expand Down
8 changes: 4 additions & 4 deletions docs/Classes.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ <h1>Classes</h1>
<li class="item">
<div>
<code>
<a name="/s:C12UIEmptyState16UIEmptyStateView"></a>
<a name="/c:@M@UIEmptyState@objc(cs)UIEmptyStateView"></a>
<a name="//apple_ref/swift/Class/UIEmptyStateView" class="dashAnchor"></a>
<a class="token" href="#/s:C12UIEmptyState16UIEmptyStateView">UIEmptyStateView</a>
<a class="token" href="#/c:@M@UIEmptyState@objc(cs)UIEmptyStateView">UIEmptyStateView</a>
</code>
</div>
<div class="height-container">
Expand Down Expand Up @@ -135,8 +135,8 @@ <h4>Declaration</h4>
</article>
</div>
<section class="footer">
<p>&copy; 2017 <a class="link" href="https://luispadron.com" target="_blank" rel="external">Luis</a>. All rights reserved. (Last updated: 2017-04-03)</p>
<p>Generated by <a class="link" href="https://github.com/realm/jazzy" target="_blank" rel="external">jazzy ♪♫ v0.7.5</a>, a <a class="link" href="http://realm.io" target="_blank" rel="external">Realm</a> project.</p>
<p>&copy; 2017 <a class="link" href="https://luispadron.com" target="_blank" rel="external">Luis</a>. All rights reserved. (Last updated: 2017-07-14)</p>
<p>Generated by <a class="link" href="https://github.com/realm/jazzy" target="_blank" rel="external">jazzy ♪♫ v0.8.3</a>, a <a class="link" href="http://realm.io" target="_blank" rel="external">Realm</a> project.</p>
</section>
</body>
</div>
Expand Down
Loading

0 comments on commit ca67239

Please sign in to comment.