The XestiMonitors framework provides nearly 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 all UIKit
notifications:
-
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 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 additional 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.
Full reference documentation is available courtesy of Jazzy.
- iOS 8.0+
- Xcode 8.0+
- Swift 3.0+
XestiMonitors is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'XestiMonitors'
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!
XestiMonitors provides seven monitor classes that you can use to observe common events generated by the system about the app:
- ApplicationStateMonitor to monitor the app for changes to its runtime state.
- BackgroundRefreshMonitor to monitor the app for changes to its status for downloading content in the background.
- MemoryMonitor to monitor the app for memory warnings from the operating system.
- ProtectedDataMonitor to monitor the app for changes to the accessibility of protected files.
- ScreenshotMonitor to monitor the app for screenshots.
- StatusBarMonitor to monitor the app for changes to the orientation of its user interface or to the frame of the status bar.
- TimeMonitor to monitor the app for significant changes in time.
XestiMonitors also 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.
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: NSNotification) {
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: NSNotification) {
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?
Best of all, the XestiMonitors framework provides several ways to create your own custom monitors quite easily.
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.
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().
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 {
notificationCenter.addObserver(self, selector: #selector(hoobieDidChange(_:)),
name: .TeraHoobieDidChange, object: hoobie)
return true
}
func hoobieDidChange(_ notification: NSNotification) {
handler()
}
}
Note: Be sure to invoke the superclass implementations of both addNotificationObservers(_:) and removeNotificationObservers(_:) in your overrides.
J. G. Pusey (ebardx@gmail.com)
XestiMonitors is available under the MIT license.