XestiMonitors 2.0.1

XestiMonitors 2.0.1

TestsTested
LangLanguage SwiftSwift
License MIT
ReleasedLast Release Jan 2018
SwiftSwift Version 4.0
SPMSupports SPM

Maintained by J. G. Pusey.


Downloads

Total727
Week19
Month92

Installs

Apps14
Apps WeekApps This Week 5
Test Targets1
powered by Segment

GitHub

Stars236
Watchers5
Forks4
Issues0
Contributors1
Pull Requests0

Code

Files24
LOCLines of Code 1,108


  • By
  • J. G. Pusey

XestiMonitors

Overview

The XestiMonitors framework provides more than a dozen fully-functional monitor classes right out of the box that make it easy for your iOS app to detect and respond to many common system-generated events.

You can think of XestiMonitors as a better way to manage the most common iOS notifications. At present, XestiMonitors provides “wrappers” around most UIKit notifications:

  • Accessibility-related — NEW!

    • UIAccessibilityAnnouncementDidFinish
    • UIAccessibilityAssistiveTouchStatusDidChange
    • UIAccessibilityBoldTextStatusDidChange
    • UIAccessibilityClosedCaptioningStatusDidChange
    • UIAccessibilityDarkerSystemColorsStatusDidChange
    • UIAccessibilityGrayscaleStatusDidChange
    • UIAccessibilityGuidedAccessStatusDidChange
    • UIAccessibilityHearingDevicePairedEarDidChange
    • UIAccessibilityInvertColorsStatusDidChange
    • UIAccessibilityMonoAudioStatusDidChange
    • UIAccessibilityReduceMotionStatusDidChange
    • UIAccessibilityReduceTransparencyStatusDidChange
    • UIAccessibilityShakeToUndoDidChange
    • UIAccessibilitySpeakScreenStatusDidChange
    • UIAccessibilitySpeakSelectionStatusDidChange
    • UIAccessibilitySwitchControlStatusDidChange
    • UIAccessibilityVoiceOverStatusChanged

    See Accessibility Monitors for details.

  • Application-related

    • UIApplicationBackgroundRefreshStatusDidChange
    • UIApplicationDidBecomeActive
    • UIApplicationDidChangeStatusBarFrame
    • UIApplicationDidChangeStatusBarOrientation
    • UIApplicationDidEnterBackground
    • UIApplicationDidFinishLaunching
    • UIApplicationDidReceiveMemoryWarning
    • UIApplicationProtectedDataDidBecomeAvailable
    • UIApplicationProtectedDataWillBecomeUnavailable
    • UIApplicationSignificantTimeChange
    • UIApplicationUserDidTakeScreenshot
    • UIApplicationWillChangeStatusBarFrame
    • UIApplicationWillChangeStatusBarOrientation
    • UIApplicationWillEnterForeground
    • UIApplicationWillResignActive
    • UIApplicationWillTerminate

    See Application Monitors for details.

  • Device-related

    • UIDeviceBatteryLevelDidChange
    • UIDeviceBatteryStateDidChange
    • UIDeviceOrientationDidChange
    • UIDeviceProximityStateDidChange

    See Device Monitors for details.

  • Keyboard-related

    • UIKeyboardDidChangeFrame
    • UIKeyboardDidHide
    • UIKeyboardDidShow
    • UIKeyboardWillChangeFrame
    • UIKeyboardWillHide
    • UIKeyboardWillShow

    See Other Monitors for details.

XestiMonitors also provides a “wrapper” around CMMotionManager to make it easier for your app to obtain both raw and processed motion measurements from the device. See Motion Monitors for details.

XestiMonitors also provides a “wrapper” around SCNetworkReachability to make it super easy for your app to determine the reachability of a target host. See Other Monitors for details.

Additional monitors targeting even more parts of iOS will be rolled out in future releases of XestiMonitor!

Finally, XestiMonitors is extensible—you can easily create your own custom monitors. See Custom Monitors for details.

Reference Documentation

Full reference documentation is available courtesy of Jazzy.

Requirements

  • iOS 8.0+
  • Xcode 8.0+
  • Swift 3.0+

Installation

XestiMonitors is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'XestiMonitors'

Usage

All monitor classes conform to the Monitor protocol, thus enabling you to create arrays of monitors that can be started or stopped uniformly—fewer lines of code!

For example, in a view controller, you can lazily instantiate several monitors and, in addition, lazily instantiate an array variable containing these monitors:

import XestiMonitors

lazy var keyboardMonitor: KeyboardMonitor = KeyboardMonitor { [weak self] in
    // do something…
}
lazy var memoryMonitor: MemoryMonitor = MemoryMonitor { [weak self] in
    // do something…
}
lazy var orientationMonitor: OrientationMonitor = OrientationMonitor { [weak self] in
    // do something…
}
lazy var monitors: [Monitor] = [self.keyboardMonitor,
                                self.memoryMonitor,
                                self.orientationMonitor]

Then, in the viewWillAppear(_:) and viewWillDisappear(_:) methods, you can simply start or stop all these monitors with a single line of code:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    monitors.forEach { $0.startMonitoring() }
}

override func viewWillDisappear(_ animated: Bool) {
    monitors.forEach { $0.stopMonitoring() }
    super.viewWillDisappear(animated)
}

Easy peasy!

Application Monitors

XestiMonitors provides seven monitor classes that you can use to observe common events generated by the system about the app:

Device Monitors

XestiMonitors provides three monitor classes that you can use to detect changes in the characteristics of the device:

  • BatteryMonitor to monitor the device for changes to the charge state and charge level of its battery.
  • OrientationMonitor to monitor the device for changes to its physical orientation.
  • ProximityMonitor to monitor the device for changes to the state of its proximity sensor.

Motion Monitors

XestiMonitors provides four monitor classes that you can use to obtain raw and processed motion measurements from the device:

  • AccelerometerMonitor to monitor the device’s accelerometer for periodic raw measurements of the acceleration along the three spatial axes.
  • DeviceMotionMonitor to monitor the device’s accelerometer, gyroscope, and magnetometer for periodic raw measurements which are processed into device motion measurements.
  • GyroMonitor to monitor the device’s gyroscope for periodic raw measurements of the rotation rate around the three spatial axes.
  • MagnetometerMonitor to monitor the device’s magnetometer for periodic raw measurements of the magnetic field around the three spatial axes.

Accessibility Monitors

XestiMonitors provides three monitor classes that you can use to observe accessibility events generated by the system:

Other Monitors

In addition, XestiMonitors provides two other monitors:

  • KeyboardMonitor to monitor the keyboard for changes to its visibility or to its frame.
  • ReachabilityMonitor to monitor a network node name or address for changes to its reachability.

KeyboardMonitor is especially handy in removing lots of boilerplate code from your app. This is how keyboard monitoring is typically handled in a custom view controller:

func keyboardWillHide(_ notification: Notification) {
    let userInfo = notification.userInfo
    var animationDuration: TimeInterval = 0
    if let value = (userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue {
        animationDuration = value
    }
    constraint.constant = 0
    UIView.animate(withDuration: animationDuration) {
        self.view.layoutIfNeeded()
    }
}

func keyboardWillShow(_ notification: Notification) {
    let userInfo = notification.userInfo
    var animationDuration: TimeInterval = 0
    if let value = (userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue {
        animationDuration = value
    }
    var frameEnd = CGRect.zero
    if let value = (userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
        frameEnd = value
    }
    constraint.constant = frameEnd.height
    UIView.animate(withDuration: animationDuration) {
        self.view.layoutIfNeeded()
    }
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    let nc = NotificationCenter.`default`
    nc.addObserver(self, selector: #selector(keyboardWillHide(_:)),
                   name: .UIKeyboardWillHide, object: nil)
    nc.addObserver(self, selector: #selector(keyboardWillShow(_:)),
                   name: .UIKeyboardWillShow, object: nil)
}

override func viewWillDisappear(_ animated: Bool) {
    NotificationCenter.`default`.removeObserver(self)
    super.viewWillDisappear(animated)
}

And this is the XestiMonitors way using KeyboardMonitor:

import XestiMonitors

lazy var keyboardMonitor: KeyboardMonitor = KeyboardMonitor { [weak self] event in
    guard let constraint = self?.constraint,
          let view = self?.view else { return }
    switch event {
    case let .willHide(info):
        constraint.constant = 0
        UIView.animate(withDuration: info.animationDuration) {
            view.layoutIfNeeded()
        }
    case let .willShow(info):
        constraint.constant = info.frameEnd.height
        UIView.animate(withDuration: info.animationDuration) {
            view.layoutIfNeeded()
        }
    default:
        break
    }
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    keyboardMonitor.startMonitoring()
}

override func viewWillDisappear(_ animated: Bool) {
    keyboardMonitor.stopMonitoring()
    super.viewWillDisappear(animated)
}

What’s in your wallet?

Custom Monitors

Best of all, the XestiMonitors framework provides several ways to create your own custom monitors quite easily.

Implementing the Monitor Protocol

You can create a new class, or extend an existing class, that conforms to the Monitor protocol. You need only implement the startMonitoring() and stopMonitoring() methods, as well as the isMonitoring property:

import XestiMonitors

extension MegaHoobieWatcher: Monitor {
    var isMonitoring: Bool { return watchingForHoobiesCount() > 0 }

    func startMonitoring() -> Bool {
        guard !isMonitoring else { return false }
        beginWatchingForHoobies()
        return isMonitoring
    }

    func stopMonitoring() -> Bool {
        guard isMonitoring else { return false }
        endWatchingForHoobies()
        return !isMonitoring
    }
}

Note: The guard statements in both startMonitoring() and stopMonitoring() protect against starting or stopping the monitor if it is in the incorrect state. This is considered good coding practice.

Subclassing the BaseMonitor Class

Typically, you will want to create a subclass of BaseMonitor. The advantage of using this abstract base class is that the basic guard logic is taken care of for you. Specifically, the startMonitoring() method does not attempt to start the monitor if it is already active, and the stopMonitoring() method does not attempt to stop the monitor if it is not active. Instead of directly implementing the required protocol methods and properties, you need only override the configureMonitor() and cleanupMonitor() methods of this base class. In fact, you will not be able to override the startMonitoring() and stopMonitoring() methods or the isMonitoring property—they are declared final in BaseMonitor.

import XestiMonitors

class GigaHoobieMonitor: BaseMonitor {
    let handler: (Float) -> Void
    let hoobie: GigaHoobie

    init(_ hoobie: GigaHoobie, handler: @escaping (Float) -> Void) {
        self.handler = handler
        self.hoobie = hoobie
    }

    override func configureMonitor() -> Bool {
        guard super.configureMonitor() else { return false }
        addObserver(self, forKeyPath: #keyPath(hoobie.nefariousActivityLevel),
                    options: .initial, context: nil)
        return true
    }

    override func cleanupMonitor() -> Bool {
        removeObserver(self, forKeyPath: #keyPath(hoobie.nefariousActivityLevel))
        return super.cleanupMonitor()
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?,
                               change: [NSKeyValueChangeKey : Any]?,
                               context: UnsafeMutableRawPointer?) {
        guard let keyPath = keyPath else { return }
        if keyPath == #keyPath(hoobie.nefariousActivityLevel) {
            handler(hoobie.nefariousActivityLevel)
        }
    }
}

Note: Be sure to invoke the superclass implementations of both configureMonitor() and cleanupMonitor().

Subclassing the BaseNotificationMonitor Class

If your custom monitor determines events by observing notifications, you should consider creating a subclass of BaseNotificationMonitor instead. In most cases you need only override the addNotificationObservers(_:) method. You can also override the removeNotificationObservers(_:) method if you require extra cleanup when the notification observers are removed upon stopping the monitor. Although this base class inherits from BaseMonitor, you will not be able to override the configureMonitor() and cleanupMonitor() methods—they are declared final in BaseNotificationMonitor.

import XestiMonitors

class TeraHoobieMonitor: BaseNotificationMonitor {
    let handler: () -> Void
    let hoobie: TeraHoobie

    init(_ hoobie: TeraHoobie, handler: @escaping () -> Void) {
        self.handler = handler
        self.hoobie = hoobie
    }

    override func addNotificationObservers(_ notificationCenter: NotificationCenter) -> Bool {
        guard super.addNotificationObservers(notificationObserver) else { return false }
        notificationCenter.addObserver(self, selector: #selector(hoobieDidChange(_:)),
                                       name: .TeraHoobieDidChange, object: hoobie)
        return true
    }

    func hoobieDidChange(_ notification: Notification) {
        handler()
    }
}

Note: Be sure to invoke the superclass implementations of both addNotificationObservers(_:) and removeNotificationObservers(_:) in your overrides.

Credits

J. G. Pusey ([email protected])

License

XestiMonitors is available under the MIT license.