         
         center.addObserver(self, selector: #selector(keyboardWillHide), name: .UIKeyboardWillHide, object: nil)
         center.addObserver(self, selector: #selector(keyboardDidHide), name: .UIKeyboardDidHide, object: nil)
-    
+        
         center.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: .UIKeyboardWillChangeFrame, object: nil)
         center.addObserver(self, selector: #selector(keyboardDidChangeFrame), name: .UIKeyboardDidChangeFrame, object: nil)
-        
     }
     
+    /// Stops listening to keyboard events. Callback closures won't be cleared, thus calling `start()` again will resume calling previously set event handlers.
     public func stop() {
         // TODO: this needs a bit more work
         let center = NotificationCenter.default
         center.removeObserver(self)
     }
     
+    /// Clears all event handlers. Equivalent of setting `nil` for all events.
     public func clear() {
         callbacks.removeAll()
     }


import UIKit

public class Typist: NSObject {
    
    static public let shared = Typist()
    
    public struct KeyboardOptions {
        /// `Bool` that identifies whether the keyboard belongs to the current app. With multitasking on iPad, all visible apps are notified when the keyboard appears and disappears. The value of this key is `true` for the app that caused the keyboard to appear and `false` for any other apps.
        let belongsToCurrentApp: Bool
        
        /// `CGRect` that identifies the start frame of the keyboard in screen coordinates. These coordinates do not take into account any rotation factors applied to the window’s contents as a result of interface orientation changes. Thus, you may need to convert the rectangle to window coordinates (using the `convertRect:fromWindow:` method) or to view coordinates (using the `convertRect:fromView:` method) before using it.
        let startFrame: CGRect
        
        /// `CGRect` that identifies the end frame of the keyboard in screen coordinates. These coordinates do not take into account any rotation factors applied to the window’s contents as a result of interface orientation changes. Thus, you may need to convert the rectangle to window coordinates (using the `convertRect:fromWindow:` method) or to view coordinates (using the `convertRect:fromView:` method) before using it.
        let endFrame: CGRect
        
        /// `UIViewAnimationCurve` constant that defines how the keyboard will be animated onto or off the screen.
        let animationCurve: UIViewAnimationCurve
        
        /// `Double` containing a double that identifies the duration of the animation in seconds.
        let animationDuration: Double
    }
    
    
    public typealias TypistCallback = (KeyboardOptions) -> ()
    
    public enum KeyboardEvent {
        case willShow
        case didShow
        case willHide
        case didHide
        case willChangeFrame
        case didChangeFrame
    }
    
    public func on(event: KeyboardEvent, do callback: TypistCallback?) -> Self {
        callbacks[event] = callback
        return self
    }
    
    public func start() {
        // TODO: start only notification that are needed (based on what handler are assigned)
        let center = NotificationCenter.`default`

        
        var animationCurve = UIViewAnimationCurve.linear
        if let index = (userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.intValue,
            let value = UIViewAnimationCurve(rawValue:index) {
            animationCurve = value
        }
        
