     }
 }
 
+private protocol NVActivityIndicatorPresenterState {
+    func startAnimating(presenter: NVActivityIndicatorPresenter)
+    func stopAnimating(presenter: NVActivityIndicatorPresenter)
+}
+
+private struct NVActivityIndicatorPresenterStateWaitingToStart: NVActivityIndicatorPresenterState {
+    func startAnimating(presenter: NVActivityIndicatorPresenter) {
+        guard let activityData = presenter.data else { return }
+
+        presenter.show(with: activityData)
+        presenter.state = .animating
+        presenter.waitingToStartGroup.leave()
+    }
+
+    func stopAnimating(presenter: NVActivityIndicatorPresenter) {
+        presenter.state = .stopped
+        presenter.waitingToStartGroup.leave()
+    }
+}
+
+private struct NVActivityIndicatorPresenterStateAnimating: NVActivityIndicatorPresenterState {
+    func startAnimating(presenter: NVActivityIndicatorPresenter) {
+        // Do nothing
+    }
+
+    func stopAnimating(presenter: NVActivityIndicatorPresenter) {
+        guard let activityData = presenter.data else { return }
+
+        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(activityData.minimumDisplayTime)) {
+            presenter.stopAnimating()
+        }
+        presenter.state = .waitingToStop
+    }
+}
+
+private struct NVActivityIndicatorPresenterStateWaitingToStop: NVActivityIndicatorPresenterState {
+    func startAnimating(presenter: NVActivityIndicatorPresenter) {
+        // Do nothing
+    }
+
+    func stopAnimating(presenter: NVActivityIndicatorPresenter) {
+        presenter.hide()
+        presenter.state = .stopped
+    }
+}
+
+private struct NVActivityIndicatorPresenterStateStopped: NVActivityIndicatorPresenterState {
+    func startAnimating(presenter: NVActivityIndicatorPresenter) {
+        guard let activityData = presenter.data else { return }
+
+        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(activityData.displayTimeThreshold)) {
+            presenter.startAnimating(activityData)
+        }
+        presenter.state = .waitingToStart
+        presenter.waitingToStartGroup.enter()
+    }
+
+    func stopAnimating(presenter: NVActivityIndicatorPresenter) {
+        // Do nothing
+    }
+}
+
 /// Presenter that displays NVActivityIndicatorView as UI blocker.
 public final class NVActivityIndicatorPresenter {
-    private enum State {
-        case waitingToShow
-        case showed
-        case waitingToHide
-        case hidden
+    fileprivate enum State: NVActivityIndicatorPresenterState {
+        case waitingToStart
+        case animating
+        case waitingToStop
+        case stopped
+
+        var performer: NVActivityIndicatorPresenterState {
+            switch self {
+            case .waitingToStart: return NVActivityIndicatorPresenterStateWaitingToStart()
+            case .animating: return NVActivityIndicatorPresenterStateAnimating()
+            case .waitingToStop: return NVActivityIndicatorPresenterStateWaitingToStop()
+            case .stopped: return NVActivityIndicatorPresenterStateStopped()
+            }
+        }
+
+        func startAnimating(presenter: NVActivityIndicatorPresenter) {
+            performer.startAnimating(presenter: presenter)
+        }
+
+        func stopAnimating(presenter: NVActivityIndicatorPresenter) {
+            performer.stopAnimating(presenter: presenter)
+        }
     }
 
     private let restorationIdentifier = "NVActivityIndicatorViewContainer"

        guard let keyWindow = UIApplication.shared.keyWindow else { return }

        keyWindow.addSubview(containerView)
        state = .showed

        // Add constraints for `containerView`.
        ({

     - parameter data: Information package used to display UI blocker.
     */
    public final func startAnimating(_ data: ActivityData) {
        self.data = data
        state.startAnimating(presenter: self)
    }

    /**
     Remove UI blocker.
     */
    public final func stopAnimating() {
        state.stopAnimating(presenter: self)
    }

    /// Set message displayed under activity indicator view.
    ///
    /// - Parameter message: message displayed under activity indicator view.
    public final func setMessage(_ message: String?) {
        waitingToStartGroup.notify(queue: DispatchQueue.main) {
            self.messageLabel.text = message
        }
    }

    // MARK: - Helpers

    fileprivate func show(with activityData: ActivityData) {
        let containerView = UIView(frame: UIScreen.main.bounds)

        containerView.backgroundColor = activityData.backgroundColor
