├── .codecov.yml ├── .github ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .hound.yml ├── .jazzy.yaml ├── .swift-version ├── .swiftlint.yml ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Documents ├── other-wrapped-notifications.md ├── wrapped-foundation-notifications.md └── wrapped-uikit-notifications.md ├── Examples ├── XestiMonitorsDemo-iOS │ ├── Podfile │ ├── Podfile.lock │ ├── XestiMonitorsDemo.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ ├── XestiMonitorsDemo.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── XestiMonitorsDemo │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── AppIcon1024x1024.pn.png │ │ │ ├── AppIcon20x20@2x.png │ │ │ ├── AppIcon20x20@2x~ipad.png │ │ │ ├── AppIcon20x20@3x.png │ │ │ ├── AppIcon20x20~ipad.png │ │ │ ├── AppIcon29x29@2x.png │ │ │ ├── AppIcon29x29@2x~ipad.png │ │ │ ├── AppIcon29x29@3x.png │ │ │ ├── AppIcon29x29~ipad.png │ │ │ ├── AppIcon40x40@2x.png │ │ │ ├── AppIcon40x40@2x~ipad.png │ │ │ ├── AppIcon40x40@3x.png │ │ │ ├── AppIcon40x40~ipad.png │ │ │ ├── AppIcon60x60@2x.png │ │ │ ├── AppIcon60x60@3x.png │ │ │ ├── AppIcon76x76@2x~ipad.png │ │ │ ├── AppIcon76x76~ipad.png │ │ │ ├── AppIcon83.5x83.5@2x~ipad.png │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── CoreLocationViewController.swift │ │ ├── CoreMotionViewController.swift │ │ ├── Formatters.swift │ │ ├── Info.plist │ │ ├── MasterViewController.swift │ │ ├── OtherViewController.swift │ │ ├── UIKitAccessibilityViewController.swift │ │ ├── UIKitApplicationViewController.swift │ │ ├── UIKitDeviceViewController.swift │ │ ├── UIKitOtherViewController.swift │ │ ├── UIKitScreenViewController.swift │ │ ├── UIKitTextViewController.swift │ │ └── XestiMonitorsDemo.entitlements └── XestiMonitorsDemo-tvOS │ ├── Podfile │ ├── Podfile.lock │ ├── XestiMonitorsDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── XestiMonitorsDemo.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── XestiMonitorsDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── App Icon & Top Shelf Image.brandassets │ │ ├── App Icon - App Store.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── App Icon.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Top Shelf Image Wide.imageset │ │ │ └── Contents.json │ │ └── Top Shelf Image.imageset │ │ │ └── Contents.json │ ├── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json │ ├── Base.lproj │ └── Main.storyboard │ ├── CoreLocationViewController.swift │ ├── Formatters.swift │ ├── Info.plist │ ├── MasterViewController.swift │ ├── OtherViewController.swift │ ├── UIKitAccessibilityViewController.swift │ ├── UIKitApplicationViewController.swift │ ├── UIKitOtherViewController.swift │ └── UIKitTextViewController.swift ├── LICENSE.md ├── Package.swift ├── README.md ├── Scripts ├── generate_docs.sh └── uniquify_projects.sh ├── Sources ├── Core │ ├── Base │ │ ├── BaseMonitor.swift │ │ ├── BaseNotificationMonitor.swift │ │ └── Monitor.swift │ ├── CoreLocation │ │ ├── BeaconRangingMonitor.swift │ │ ├── HeadingMonitor.swift │ │ ├── LocationAuthorizationMonitor.swift │ │ ├── LocationManagerDelegateAdapter.swift │ │ ├── RegionMonitor.swift │ │ ├── SignificantLocationMonitor.swift │ │ ├── StandardLocationMonitor.swift │ │ └── VisitMonitor.swift │ ├── CoreMotion │ │ ├── AccelerometerMonitor.swift │ │ ├── AltimeterMonitor.swift │ │ ├── DeviceMotionMonitor.swift │ │ ├── GyroscopeMonitor.swift │ │ ├── MagnetometerMonitor.swift │ │ ├── MotionActivityMonitor.swift │ │ └── PedometerMonitor.swift │ ├── DependencyInjection │ │ ├── AccessibilityStatus.swift │ │ ├── AccessibilityStatusInjection.swift │ │ ├── AltimeterInjection.swift │ │ ├── ApplicationInjection.swift │ │ ├── DeviceInjection.swift │ │ ├── FileManagerInjection.swift │ │ ├── FileSystem.swift │ │ ├── FileSystemInjection.swift │ │ ├── FileSystemObjectInjection.swift │ │ ├── LocationManagerInjection.swift │ │ ├── MotionActivityManagerInjection.swift │ │ ├── MotionManagerInjection.swift │ │ ├── NetworkReachability.swift │ │ ├── NetworkReachabilityInjection.swift │ │ ├── NotificationCenterInjection.swift │ │ ├── PedometerInjection.swift │ │ └── ProcessInfoInjection.swift │ ├── Extensions │ │ └── CMAcceleration+DeviceOrientation.swift │ ├── Foundation │ │ ├── BundleClassLoadMonitor.swift │ │ ├── BundleResourceRequestMonitor.swift │ │ ├── CalendarDayMonitor.swift │ │ ├── CurrentLocaleMonitor.swift │ │ ├── ExtensionHostMonitor.swift │ │ ├── HTTPCookieStorageMonitor.swift │ │ ├── MetadataQueryMonitor.swift │ │ ├── PortMonitor.swift │ │ ├── ProcessInfoPowerStateMonitor.swift │ │ ├── ProcessInfoThermalStateMonitor.swift │ │ ├── SystemClockMonitor.swift │ │ ├── SystemTimeZoneMonitor.swift │ │ ├── URLCredentialStorageMonitor.swift │ │ ├── UbiquitousKeyValueStoreMonitor.swift │ │ ├── UbiquityIdentityMonitor.swift │ │ ├── UndoManagerMonitor.swift │ │ └── UserDefaultsMonitor.swift │ ├── Other │ │ ├── FileSystemObjectMonitor.swift │ │ └── NetworkReachabilityMonitor.swift │ └── UIKit │ │ ├── Accessibility │ │ ├── AccessibilityAnnouncementMonitor.swift │ │ ├── AccessibilityElementMonitor.swift │ │ └── AccessibilityStatusMonitor.swift │ │ ├── Application │ │ ├── ApplicationStateMonitor.swift │ │ ├── BackgroundRefreshMonitor.swift │ │ ├── MemoryMonitor.swift │ │ ├── ProtectedDataMonitor.swift │ │ ├── ScreenshotMonitor.swift │ │ ├── StatusBarMonitor.swift │ │ └── TimeMonitor.swift │ │ ├── Device │ │ ├── BatteryMonitor.swift │ │ ├── OrientationMonitor.swift │ │ └── ProximityMonitor.swift │ │ ├── Other │ │ ├── ContentSizeCategoryMonitor.swift │ │ ├── DocumentStateMonitor.swift │ │ ├── FocusMonitor.swift │ │ ├── KeyboardMonitor.swift │ │ ├── MenuControllerMonitor.swift │ │ ├── PasteboardMonitor.swift │ │ ├── TableViewSelectionMonitor.swift │ │ ├── ViewControllerShowDetailTargetMonitor.swift │ │ └── WindowMonitor.swift │ │ ├── Screen │ │ ├── ScreenBrightnessMonitor.swift │ │ ├── ScreenCapturedMonitor.swift │ │ ├── ScreenConnectionMonitor.swift │ │ └── ScreenModeMonitor.swift │ │ └── Text │ │ ├── TextFieldTextMonitor.swift │ │ ├── TextInputModeMonitor.swift │ │ ├── TextStorageMonitor.swift │ │ └── TextViewTextMonitor.swift ├── Info-tvOS.plist ├── Info.plist └── XestiMonitors.h ├── Tests ├── Base │ ├── BaseMonitorTests.swift │ └── BaseNotificationMonitorTests.swift ├── CoreLocation │ ├── BeaconRangingMonitorTests.swift │ ├── HeadingMonitorTests.swift │ ├── LocationAuthorizationMonitorTests.swift │ ├── RegionMonitorTests.swift │ ├── SignificantLocationMonitorTests.swift │ ├── StandardLocationMonitorTests.swift │ └── VisitMonitorTests.swift ├── CoreMotion │ ├── AccelerometerMonitorTests.swift │ ├── AltimeterMonitorTests.swift │ ├── DeviceMotionMonitorTests.swift │ ├── GyroscopeMonitorTests.swift │ ├── MagnetometerMonitorTests.swift │ ├── MotionActivityMonitorTests.swift │ └── PedometerMonitorTests.swift ├── Foundation │ ├── BundleClassLoadMonitorTests.swift │ ├── BundleResourceRequestMonitorTests.swift │ ├── CalendarDayMonitorTests.swift │ ├── CurrentLocaleMonitorTests.swift │ ├── ExtensionHostMonitorTests.swift │ ├── HTTPCookieStorageMonitorTests.swift │ ├── MetadataQueryMonitorTests.swift │ ├── PortMonitorTests.swift │ ├── ProcessInfoPowerStateMonitorTests.swift │ ├── ProcessInfoThermalStateMonitorTests.swift │ ├── SystemClockMonitorTests.swift │ ├── SystemTimeZoneMonitorTests.swift │ ├── URLCredentialStorageMonitorTests.swift │ ├── UbiquitousKeyValueStoreMonitorTests.swift │ ├── UbiquityIdentityMonitorTests.swift │ ├── UndoManagerMonitorTests.swift │ └── UserDefaultsMonitorTests.swift ├── Info-tvOS.plist ├── Info.plist ├── Miscellaneous │ └── CMAccelerationExtensionsTests.swift ├── Mock │ ├── MockAccessibilityStatus.swift │ ├── MockAltimeter.swift │ ├── MockApplication.swift │ ├── MockDevice.swift │ ├── MockFileManager.swift │ ├── MockFileSystem.swift │ ├── MockFileSystemObject.swift │ ├── MockLocationManager.swift │ ├── MockMotionActivityManager.swift │ ├── MockMotionManager.swift │ ├── MockNetworkReachability.swift │ ├── MockNotificationCenter.swift │ ├── MockPedometer.swift │ ├── MockProcessInfo.swift │ ├── UIFocusUpdateContext+Neutered.h │ ├── UIFocusUpdateContext+Neutered.m │ └── XestiMonitorsTests-iOS-Bridging-Header.h ├── Other │ ├── FileSystemObjectMonitorTests.swift │ └── NetworkReachabilityMonitorTests.swift └── UIKit │ ├── Accessibility │ ├── AccessibilityAnnouncementMonitorTests.swift │ ├── AccessibilityElementMonitorTests.swift │ └── AccessibilityStatusMonitorTests.swift │ ├── Application │ ├── ApplicationStateMonitorTests.swift │ ├── BackgroundRefreshMonitorTests.swift │ ├── MemoryMonitorTests.swift │ ├── ProtectedDataMonitorTests.swift │ ├── ScreenshotMonitorTests.swift │ ├── StatusBarMonitorTests.swift │ └── TimeMonitorTests.swift │ ├── Device │ ├── BatteryMonitorTests.swift │ ├── OrientationMonitorTests.swift │ └── ProximityMonitorTests.swift │ ├── Other │ ├── ContentSizeCategoryMonitorTests.swift │ ├── DocumentStateMonitorTests.swift │ ├── FocusMonitorTests.swift │ ├── KeyboardMonitorTests.swift │ ├── MenuControllerMonitorTests.swift │ ├── PasteboardMonitorTests.swift │ ├── TableViewSelectionMonitorTests.swift │ ├── ViewControllerShowDetailTargetMonitorTests.swift │ └── WindowMonitorTests.swift │ ├── Screen │ ├── ScreenBrightnessMonitorTests.swift │ ├── ScreenCapturedMonitorTests.swift │ ├── ScreenConnectionMonitorTests.swift │ └── ScreenModeMonitorTests.swift │ └── Text │ ├── TextFieldTextMonitorTests.swift │ ├── TextInputModeMonitorTests.swift │ ├── TextStorageMonitorTests.swift │ └── TextViewTextMonitorTests.swift ├── XestiMonitors.podspec ├── XestiMonitors.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── XestiMonitors-iOS.xcscheme │ ├── XestiMonitors-macOS.xcscheme │ ├── XestiMonitors-tvOS.xcscheme │ └── XestiMonitors-watchOS.xcscheme └── docs ├── Base Monitor Classes.html ├── Classes ├── AccelerometerMonitor.html ├── AccelerometerMonitor │ ├── Event.html │ └── Info.html ├── AccessibilityAnnouncementMonitor.html ├── AccessibilityAnnouncementMonitor │ ├── Event.html │ └── Info.html ├── AccessibilityElementMonitor.html ├── AccessibilityElementMonitor │ ├── Event.html │ └── Info.html ├── AccessibilityStatusMonitor.html ├── AccessibilityStatusMonitor │ ├── Event.html │ └── Options.html ├── AltimeterMonitor.html ├── AltimeterMonitor │ ├── Event.html │ └── Info.html ├── ApplicationStateMonitor.html ├── ApplicationStateMonitor │ ├── Event.html │ └── Options.html ├── BackgroundRefreshMonitor.html ├── BackgroundRefreshMonitor │ └── Event.html ├── BaseMonitor.html ├── BaseNotificationMonitor.html ├── BatteryMonitor.html ├── BatteryMonitor │ ├── Event.html │ └── Options.html ├── BeaconRangingMonitor.html ├── BeaconRangingMonitor │ ├── Event.html │ └── Info.html ├── BundleClassLoadMonitor.html ├── BundleClassLoadMonitor │ └── Event.html ├── BundleResourceRequestMonitor.html ├── BundleResourceRequestMonitor │ └── Event.html ├── CalendarDayMonitor.html ├── CalendarDayMonitor │ └── Event.html ├── ContentSizeCategoryMonitor.html ├── ContentSizeCategoryMonitor │ └── Event.html ├── CurrentLocaleMonitor.html ├── CurrentLocaleMonitor │ └── Event.html ├── DeviceMotionMonitor.html ├── DeviceMotionMonitor │ ├── Event.html │ └── Info.html ├── DocumentStateMonitor.html ├── DocumentStateMonitor │ └── Event.html ├── ExtensionHostMonitor.html ├── ExtensionHostMonitor │ ├── Event.html │ └── Options.html ├── FileSystemObjectMonitor.html ├── FileSystemObjectMonitor │ ├── Event.html │ └── Options.html ├── FocusMonitor.html ├── FocusMonitor │ ├── Event.html │ ├── Info.html │ └── Options.html ├── GyroscopeMonitor.html ├── GyroscopeMonitor │ ├── Event.html │ └── Info.html ├── HTTPCookiesStorageMonitor.html ├── HTTPCookiesStorageMonitor │ ├── Event.html │ └── Options.html ├── HeadingMonitor.html ├── HeadingMonitor │ ├── Event.html │ └── Info.html ├── KeyboardMonitor.html ├── KeyboardMonitor │ ├── Event.html │ ├── Info.html │ └── Options.html ├── LocationAuthorizationMonitor.html ├── LocationAuthorizationMonitor │ ├── Event.html │ └── Info.html ├── MagnetometerMonitor.html ├── MagnetometerMonitor │ ├── Event.html │ └── Info.html ├── MemoryMonitor.html ├── MemoryMonitor │ └── Event.html ├── MenuControllerMonitor.html ├── MenuControllerMonitor │ ├── Event.html │ └── Options.html ├── MetadataQueryMonitor.html ├── MetadataQueryMonitor │ ├── Event.html │ ├── Info.html │ └── Options.html ├── MotionActivityMonitor.html ├── MotionActivityMonitor │ ├── Event.html │ └── Info.html ├── NetworkReachabilityMonitor.html ├── NetworkReachabilityMonitor │ ├── Event.html │ └── Status.html ├── OrientationMonitor.html ├── OrientationMonitor │ └── Event.html ├── PasteboardMonitor.html ├── PasteboardMonitor │ ├── Changes.html │ ├── Event.html │ └── Options.html ├── PedometerMonitor.html ├── PedometerMonitor │ ├── Event.html │ └── Info.html ├── PortMonitor.html ├── PortMonitor │ └── Event.html ├── ProcessInfoPowerStateMonitor.html ├── ProcessInfoPowerStateMonitor │ └── Event.html ├── ProcessInfoThermalStateMonitor.html ├── ProcessInfoThermalStateMonitor │ └── Event.html ├── ProtectedDataMonitor.html ├── ProtectedDataMonitor │ ├── Event.html │ └── Options.html ├── ProximityMonitor.html ├── ProximityMonitor │ └── Event.html ├── RegionMonitor.html ├── RegionMonitor │ ├── Event.html │ └── Info.html ├── ScreenBrightnessMonitor.html ├── ScreenBrightnessMonitor │ └── Event.html ├── ScreenCapturedMonitor.html ├── ScreenCapturedMonitor │ └── Event.html ├── ScreenConnectionMonitor.html ├── ScreenConnectionMonitor │ ├── Event.html │ └── Options.html ├── ScreenModeMonitor.html ├── ScreenModeMonitor │ └── Event.html ├── ScreenshotMonitor.html ├── ScreenshotMonitor │ └── Event.html ├── SignificantLocationMonitor.html ├── SignificantLocationMonitor │ ├── Event.html │ └── Info.html ├── StandardLocationMonitor.html ├── StandardLocationMonitor │ ├── Event.html │ └── Info.html ├── StatusBarMonitor.html ├── StatusBarMonitor │ ├── Event.html │ └── Options.html ├── SystemClockMonitor.html ├── SystemClockMonitor │ └── Event.html ├── SystemTimeZoneMonitor.html ├── SystemTimeZoneMonitor │ └── Event.html ├── TableViewSelectionMonitor.html ├── TableViewSelectionMonitor │ └── Event.html ├── TextFieldTextMonitor.html ├── TextFieldTextMonitor │ ├── Event.html │ └── Options.html ├── TextInputModeMonitor.html ├── TextInputModeMonitor │ └── Event.html ├── TextStorageMonitor.html ├── TextStorageMonitor │ ├── Event.html │ └── Options.html ├── TextViewTextMonitor.html ├── TextViewTextMonitor │ ├── Event.html │ └── Options.html ├── TimeMonitor.html ├── TimeMonitor │ └── Event.html ├── URLCredentialStorageMonitor.html ├── URLCredentialStorageMonitor │ └── Event.html ├── UbiquitousKeyValueStoreMonitor.html ├── UbiquitousKeyValueStoreMonitor │ ├── Event.html │ └── Options.html ├── UbiquityIdentityMonitor.html ├── UbiquityIdentityMonitor │ └── Event.html ├── UndoManagerMonitor.html ├── UndoManagerMonitor │ ├── Event.html │ └── Options.html ├── UserDefaultsMonitor.html ├── UserDefaultsMonitor │ ├── Event.html │ └── Options.html ├── ViewControllerShowDetailTargetMonitor.html ├── ViewControllerShowDetailTargetMonitor │ └── Event.html ├── VisitMonitor.html ├── VisitMonitor │ ├── Event.html │ └── Info.html ├── WindowMonitor.html └── WindowMonitor │ ├── Event.html │ └── Options.html ├── Core Location Monitor Classes.html ├── Core Motion Monitor Classes.html ├── Extensions.html ├── Extensions └── CMAcceleration.html ├── Foundation Monitor Classes.html ├── Other Monitor Classes.html ├── Other UIKit Monitor Classes.html ├── Protocols.html ├── Protocols └── Monitor.html ├── UIKit Accessibility Monitor Classes.html ├── UIKit Application Monitor Classes.html ├── UIKit Device Monitor Classes.html ├── UIKit Screen Monitor Classes.html ├── UIKit Text Monitor Classes.html ├── badge.svg ├── css ├── highlight.css └── jazzy.css ├── docsets ├── XestiMonitors.docset │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ ├── Documents │ │ ├── Base Monitor Classes.html │ │ ├── Classes │ │ │ ├── AccelerometerMonitor.html │ │ │ ├── AccelerometerMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── AccessibilityAnnouncementMonitor.html │ │ │ ├── AccessibilityAnnouncementMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── AccessibilityElementMonitor.html │ │ │ ├── AccessibilityElementMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── AccessibilityStatusMonitor.html │ │ │ ├── AccessibilityStatusMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── AltimeterMonitor.html │ │ │ ├── AltimeterMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── ApplicationStateMonitor.html │ │ │ ├── ApplicationStateMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── BackgroundRefreshMonitor.html │ │ │ ├── BackgroundRefreshMonitor │ │ │ │ └── Event.html │ │ │ ├── BaseMonitor.html │ │ │ ├── BaseNotificationMonitor.html │ │ │ ├── BatteryMonitor.html │ │ │ ├── BatteryMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── BeaconRangingMonitor.html │ │ │ ├── BeaconRangingMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── BundleClassLoadMonitor.html │ │ │ ├── BundleClassLoadMonitor │ │ │ │ └── Event.html │ │ │ ├── BundleResourceRequestMonitor.html │ │ │ ├── BundleResourceRequestMonitor │ │ │ │ └── Event.html │ │ │ ├── CalendarDayMonitor.html │ │ │ ├── CalendarDayMonitor │ │ │ │ └── Event.html │ │ │ ├── ContentSizeCategoryMonitor.html │ │ │ ├── ContentSizeCategoryMonitor │ │ │ │ └── Event.html │ │ │ ├── CurrentLocaleMonitor.html │ │ │ ├── CurrentLocaleMonitor │ │ │ │ └── Event.html │ │ │ ├── DeviceMotionMonitor.html │ │ │ ├── DeviceMotionMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── DocumentStateMonitor.html │ │ │ ├── DocumentStateMonitor │ │ │ │ └── Event.html │ │ │ ├── ExtensionHostMonitor.html │ │ │ ├── ExtensionHostMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── FileSystemObjectMonitor.html │ │ │ ├── FileSystemObjectMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── FocusMonitor.html │ │ │ ├── FocusMonitor │ │ │ │ ├── Event.html │ │ │ │ ├── Info.html │ │ │ │ └── Options.html │ │ │ ├── GyroscopeMonitor.html │ │ │ ├── GyroscopeMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── HTTPCookiesStorageMonitor.html │ │ │ ├── HTTPCookiesStorageMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── HeadingMonitor.html │ │ │ ├── HeadingMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── KeyboardMonitor.html │ │ │ ├── KeyboardMonitor │ │ │ │ ├── Event.html │ │ │ │ ├── Info.html │ │ │ │ └── Options.html │ │ │ ├── LocationAuthorizationMonitor.html │ │ │ ├── LocationAuthorizationMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── MagnetometerMonitor.html │ │ │ ├── MagnetometerMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── MemoryMonitor.html │ │ │ ├── MemoryMonitor │ │ │ │ └── Event.html │ │ │ ├── MenuControllerMonitor.html │ │ │ ├── MenuControllerMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── MetadataQueryMonitor.html │ │ │ ├── MetadataQueryMonitor │ │ │ │ ├── Event.html │ │ │ │ ├── Info.html │ │ │ │ └── Options.html │ │ │ ├── MotionActivityMonitor.html │ │ │ ├── MotionActivityMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── NetworkReachabilityMonitor.html │ │ │ ├── NetworkReachabilityMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Status.html │ │ │ ├── OrientationMonitor.html │ │ │ ├── OrientationMonitor │ │ │ │ └── Event.html │ │ │ ├── PasteboardMonitor.html │ │ │ ├── PasteboardMonitor │ │ │ │ ├── Changes.html │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── PedometerMonitor.html │ │ │ ├── PedometerMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── PortMonitor.html │ │ │ ├── PortMonitor │ │ │ │ └── Event.html │ │ │ ├── ProcessInfoPowerStateMonitor.html │ │ │ ├── ProcessInfoPowerStateMonitor │ │ │ │ └── Event.html │ │ │ ├── ProcessInfoThermalStateMonitor.html │ │ │ ├── ProcessInfoThermalStateMonitor │ │ │ │ └── Event.html │ │ │ ├── ProtectedDataMonitor.html │ │ │ ├── ProtectedDataMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── ProximityMonitor.html │ │ │ ├── ProximityMonitor │ │ │ │ └── Event.html │ │ │ ├── RegionMonitor.html │ │ │ ├── RegionMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── ScreenBrightnessMonitor.html │ │ │ ├── ScreenBrightnessMonitor │ │ │ │ └── Event.html │ │ │ ├── ScreenCapturedMonitor.html │ │ │ ├── ScreenCapturedMonitor │ │ │ │ └── Event.html │ │ │ ├── ScreenConnectionMonitor.html │ │ │ ├── ScreenConnectionMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── ScreenModeMonitor.html │ │ │ ├── ScreenModeMonitor │ │ │ │ └── Event.html │ │ │ ├── ScreenshotMonitor.html │ │ │ ├── ScreenshotMonitor │ │ │ │ └── Event.html │ │ │ ├── SignificantLocationMonitor.html │ │ │ ├── SignificantLocationMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── StandardLocationMonitor.html │ │ │ ├── StandardLocationMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── StatusBarMonitor.html │ │ │ ├── StatusBarMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── SystemClockMonitor.html │ │ │ ├── SystemClockMonitor │ │ │ │ └── Event.html │ │ │ ├── SystemTimeZoneMonitor.html │ │ │ ├── SystemTimeZoneMonitor │ │ │ │ └── Event.html │ │ │ ├── TableViewSelectionMonitor.html │ │ │ ├── TableViewSelectionMonitor │ │ │ │ └── Event.html │ │ │ ├── TextFieldTextMonitor.html │ │ │ ├── TextFieldTextMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── TextInputModeMonitor.html │ │ │ ├── TextInputModeMonitor │ │ │ │ └── Event.html │ │ │ ├── TextStorageMonitor.html │ │ │ ├── TextStorageMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── TextViewTextMonitor.html │ │ │ ├── TextViewTextMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── TimeMonitor.html │ │ │ ├── TimeMonitor │ │ │ │ └── Event.html │ │ │ ├── URLCredentialStorageMonitor.html │ │ │ ├── URLCredentialStorageMonitor │ │ │ │ └── Event.html │ │ │ ├── UbiquitousKeyValueStoreMonitor.html │ │ │ ├── UbiquitousKeyValueStoreMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── UbiquityIdentityMonitor.html │ │ │ ├── UbiquityIdentityMonitor │ │ │ │ └── Event.html │ │ │ ├── UndoManagerMonitor.html │ │ │ ├── UndoManagerMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── UserDefaultsMonitor.html │ │ │ ├── UserDefaultsMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ │ ├── ViewControllerShowDetailTargetMonitor.html │ │ │ ├── ViewControllerShowDetailTargetMonitor │ │ │ │ └── Event.html │ │ │ ├── VisitMonitor.html │ │ │ ├── VisitMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Info.html │ │ │ ├── WindowMonitor.html │ │ │ └── WindowMonitor │ │ │ │ ├── Event.html │ │ │ │ └── Options.html │ │ ├── Core Location Monitor Classes.html │ │ ├── Core Motion Monitor Classes.html │ │ ├── Extensions.html │ │ ├── Extensions │ │ │ └── CMAcceleration.html │ │ ├── Foundation Monitor Classes.html │ │ ├── Other Monitor Classes.html │ │ ├── Other UIKit Monitor Classes.html │ │ ├── Protocols.html │ │ ├── Protocols │ │ │ └── Monitor.html │ │ ├── UIKit Accessibility Monitor Classes.html │ │ ├── UIKit Application Monitor Classes.html │ │ ├── UIKit Device Monitor Classes.html │ │ ├── UIKit Screen Monitor Classes.html │ │ ├── UIKit Text Monitor Classes.html │ │ ├── css │ │ │ ├── highlight.css │ │ │ └── jazzy.css │ │ ├── img │ │ │ ├── carat.png │ │ │ ├── dash.png │ │ │ ├── gh.png │ │ │ └── spinner.gif │ │ ├── index.html │ │ ├── js │ │ │ ├── jazzy.js │ │ │ ├── jazzy.search.js │ │ │ ├── jquery.min.js │ │ │ ├── lunr.min.js │ │ │ └── typeahead.jquery.js │ │ └── search.json │ │ └── docSet.dsidx ├── XestiMonitors.tgz └── XestiMonitors.xml ├── img ├── carat.png ├── dash.png ├── gh.png └── spinner.gif ├── index.html ├── js ├── jazzy.js ├── jazzy.search.js ├── jquery.min.js ├── lunr.min.js └── typeahead.jquery.js ├── search.json └── undocumented.json /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - Examples 3 | - Tests 4 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | - [ ] I have reviewed the [documentation](https://ebardx.github.io/XestiMonitors/) 6 | - [ ] I have searched [existing issues](https://github.com/eBardX/XestiMonitors/issues) 7 | - [ ] I am using the [latest XestiMonitors](https://github.com/eBardX/XestiMonitors/releases) 8 | 9 | 10 | 11 | 12 | #### Environment 13 | 14 | 15 | #### Steps to Reproduce 16 | 19 | 20 | #### Expected Behavior 21 | 22 | 23 | #### Actual Behavior 24 | 25 | 26 | ## Feature 27 | 28 | 29 | 30 | #### Feature Name 31 | 32 | 33 | #### Events 34 | 35 | 36 | #### Platform 37 | 38 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #### What is the purpose of this pull request? 4 | 5 | 6 | 7 | #### Background 8 | 9 | 10 | 11 | #### Checklist 12 | 13 | 14 | 15 | - [ ] Bug fix (non-breaking change which fixes an issue). 16 | - [ ] New feature (non-breaking change which adds functionality). 17 | - [ ] Breaking change (fix or feature that would cause existing functionality to change). 18 | - [ ] I have read the **CONTRIBUTING** guidelines. 19 | - [ ] My code follows the coding style of this project. 20 | - [ ] My change requires a change to the documentation. 21 | - [ ] I have documented all my changes. 22 | - [ ] I have added tests to cover my changes. 23 | - [ ] All new and existing tests pass. 24 | 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## OSX 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Icon must end with two \r 7 | Icon 8 | 9 | # Thumbnails 10 | ._* 11 | 12 | # Files that might appear in the root of a volume 13 | .DocumentRevisions-V100 14 | .fseventsd 15 | .Spotlight-V100 16 | .TemporaryItems 17 | .Trashes 18 | .VolumeIcon.icns 19 | .com.apple.timemachine.donotpresent 20 | 21 | # Directories potentially created on remote AFP share 22 | .AppleDB 23 | .AppleDesktop 24 | Network Trash Folder 25 | Temporary Items 26 | .apdisk 27 | 28 | # Xcode 29 | # 30 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 31 | 32 | ## Build generated 33 | build/ 34 | DerivedData/ 35 | 36 | ## Various settings 37 | *.pbxuser 38 | !default.pbxuser 39 | *.mode1v3 40 | !default.mode1v3 41 | *.mode2v3 42 | !default.mode2v3 43 | *.perspectivev3 44 | !default.perspectivev3 45 | xcuserdata/ 46 | 47 | ## Other 48 | *.moved-aside 49 | *.xcuserstate 50 | 51 | ## Obj-C/Swift specific 52 | *.hmap 53 | *.ipa 54 | *.dSYM.zip 55 | *.dSYM 56 | 57 | ## Playgrounds 58 | timeline.xctimeline 59 | playground.xcworkspace 60 | 61 | # Swift Package Manager 62 | # 63 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 64 | Packages/ 65 | .build/ 66 | 67 | # CocoaPods 68 | # 69 | # We recommend against adding the Pods directory to your .gitignore. However 70 | # you should judge for yourself, the pros and cons are mentioned at: 71 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 72 | # 73 | Pods/ 74 | 75 | # Carthage 76 | # 77 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 78 | Carthage/Checkouts 79 | Carthage/Build 80 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | swift: 2 | config_file: .swiftlint.yml 3 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9.3 3 | env: 4 | global: 5 | - LC_CTYPE=en_US.UTF-8 6 | - LANG=en_US.UTF-8 7 | - PROJECT=XestiMonitors.xcodeproj 8 | - IOS_FRAMEWORK_SCHEME="XestiMonitors-iOS" 9 | - OSX_FRAMEWORK_SCHEME="XestiMonitors-macOS" 10 | - TVOS_FRAMEWORK_SCHEME="XestiMonitors-tvOS" 11 | - WATCHOS_FRAMEWORK_SCHEME="XestiMonitors-watchOS" 12 | matrix: 13 | - DESTINATION="OS=9.0,name=iPhone 6s Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" RUN_TESTS="YES" 14 | - DESTINATION="OS=11.3,name=iPhone X" SCHEME="$IOS_FRAMEWORK_SCHEME" RUN_TESTS="YES" 15 | - DESTINATION="arch=x86_64" SCHEME="$OSX_FRAMEWORK_SCHEME" RUN_TESTS="YES" 16 | - DESTINATION="OS=9.2,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" RUN_TESTS="YES" 17 | - DESTINATION="OS=11.3,name=Apple TV 4K (at 1080p)" SCHEME="$TVOS_FRAMEWORK_SCHEME" RUN_TESTS="YES" 18 | - DESTINATION="OS=2.0,name=Apple Watch - 38mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" RUN_TESTS="NO" 19 | - DESTINATION="OS=4.3,name=Apple Watch Series 2 - 42mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" RUN_TESTS="NO" 20 | script: 21 | - set -o pipefail 22 | - xcodebuild -version 23 | - xcodebuild -showsdks 24 | # 25 | # Build Framework in Debug and Run Tests if specified 26 | # 27 | - if [ $RUN_TESTS == "YES" ]; then 28 | xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty -c; 29 | else 30 | xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c; 31 | fi 32 | # 33 | # Build Framework in Release and Run Tests if specified 34 | # 35 | - if [ $RUN_TESTS == "YES" ]; then 36 | xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty -c; 37 | else 38 | xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO build | xcpretty -c; 39 | fi 40 | after_success: bash <(curl -s https://codecov.io/bash) 41 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | platform :ios, '9.0' 4 | 5 | use_frameworks! 6 | 7 | target 'XestiMonitorsDemo' do 8 | pod 'XestiMonitors', :path => '../../' 9 | end 10 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - XestiMonitors (2.12.0): 3 | - XestiMonitors/Core (= 2.12.0) 4 | - XestiMonitors/Core (2.12.0) 5 | 6 | DEPENDENCIES: 7 | - XestiMonitors (from `../../`) 8 | 9 | EXTERNAL SOURCES: 10 | XestiMonitors: 11 | :path: "../../" 12 | 13 | SPEC CHECKSUMS: 14 | XestiMonitors: 8a5635a8fdf6f1909220ef93894b90f0d05130d8 15 | 16 | PODFILE CHECKSUM: af967f85b3de0f1060583f21837d6381de340343 17 | 18 | COCOAPODS: 1.5.3 19 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // XestiMonitorsDemo-iOS 4 | // 5 | // Created by J. G. Pusey on 2016-11-23. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | 12 | @UIApplicationMain 13 | public class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { 14 | 15 | // MARK: Public Instance Properties 16 | 17 | public var window: UIWindow? 18 | 19 | // MARK: UIApplicationDelegate Methods 20 | 21 | public func application(_ application: UIApplication, 22 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 23 | guard 24 | let svc = window?.rootViewController as? UISplitViewController 25 | else { return false } 26 | 27 | svc.delegate = self 28 | svc.preferredDisplayMode = .allVisible 29 | 30 | guard 31 | let nc = svc.viewControllers[svc.viewControllers.count - 1] as? UINavigationController 32 | else { return false } 33 | 34 | nc.topViewController?.navigationItem.leftBarButtonItem = svc.displayModeButtonItem 35 | 36 | return true 37 | } 38 | 39 | public func applicationDidBecomeActive(_ application: UIApplication) { 40 | } 41 | 42 | public func applicationDidEnterBackground(_ application: UIApplication) { 43 | } 44 | 45 | public func applicationWillEnterForeground(_ application: UIApplication) { 46 | } 47 | 48 | public func applicationWillResignActive(_ application: UIApplication) { 49 | } 50 | 51 | public func applicationWillTerminate(_ application: UIApplication) { 52 | } 53 | 54 | // MARK: UISplitViewControllerDelegate Methods 55 | 56 | public func targetDisplayModeForAction(in splitViewController: UISplitViewController) -> UISplitViewControllerDisplayMode { 57 | return .allVisible 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon1024x1024.pn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon1024x1024.pn.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon20x20@2x.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon20x20@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon20x20@2x~ipad.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon20x20@3x.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon20x20~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon20x20~ipad.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon29x29@2x.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon29x29@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon29x29@2x~ipad.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon29x29@3x.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon29x29~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon29x29~ipad.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon40x40@2x.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon40x40@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon40x40@2x~ipad.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon40x40@3x.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon40x40~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon40x40~ipad.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon60x60@2x.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon60x60@3x.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon76x76@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon76x76@2x~ipad.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon76x76~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon76x76~ipad.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon83.5x83.5@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Assets.xcassets/AppIcon.appiconset/AppIcon83.5x83.5@2x~ipad.png -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSLocationAlwaysAndWhenInUseUsageDescription 24 | For demonstration purposes. 25 | NSLocationAlwaysUsageDescription 26 | For demonstration purposes. 27 | NSLocationWhenInUseUsageDescription 28 | For demonstration purposes. 29 | NSMotionUsageDescription 30 | For demonstration purposes. 31 | UIBackgroundModes 32 | 33 | location 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen 37 | UIMainStoryboardFile 38 | Main 39 | UIRequiredDeviceCapabilities 40 | 41 | armv7 42 | 43 | UIStatusBarTintParameters 44 | 45 | UINavigationBar 46 | 47 | Style 48 | UIBarStyleDefault 49 | Translucent 50 | 51 | 52 | 53 | UISupportedInterfaceOrientations 54 | 55 | UIInterfaceOrientationPortrait 56 | UIInterfaceOrientationPortraitUpsideDown 57 | UIInterfaceOrientationLandscapeLeft 58 | UIInterfaceOrientationLandscapeRight 59 | 60 | UISupportedInterfaceOrientations~ipad 61 | 62 | UIInterfaceOrientationPortrait 63 | UIInterfaceOrientationPortraitUpsideDown 64 | UIInterfaceOrientationLandscapeLeft 65 | UIInterfaceOrientationLandscapeRight 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/MasterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController.swift 3 | // XestiMonitorsDemo-iOS 4 | // 5 | // Created by J. G. Pusey on 2016-11-23. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | 12 | public class MasterViewController: UITableViewController { 13 | override public func viewWillAppear(_ animated: Bool) { 14 | clearsSelectionOnViewWillAppear = splitViewController?.isCollapsed ?? false 15 | 16 | super.viewWillAppear(animated) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/OtherViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherViewController.swift 3 | // XestiMonitorsDemo-iOS 4 | // 5 | // Created by J. G. Pusey on 2016-11-23. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import XestiMonitors 11 | 12 | public class OtherViewController: UITableViewController { 13 | 14 | // MARK: Private Instance Properties 15 | 16 | @IBOutlet private weak var networkReachabilityLabel: UILabel! 17 | 18 | private lazy var networkReachabilityMonitor = NetworkReachabilityMonitor(queue: .main) { [unowned self] in 19 | self.displayNetworkReachability($0) 20 | } 21 | 22 | private lazy var monitors: [Monitor] = [networkReachabilityMonitor] 23 | 24 | // MARK: Private Instance Methods 25 | 26 | private func displayNetworkReachability(_ event: NetworkReachabilityMonitor.Event?) { 27 | if let event = event, 28 | case let .statusDidChange(status) = event { 29 | switch status { 30 | case .notReachable: 31 | networkReachabilityLabel.text = "Not reachable" 32 | 33 | case .reachableViaWiFi: 34 | networkReachabilityLabel.text = "Reachable via Wi-Fi" 35 | 36 | case .reachableViaWWAN: 37 | networkReachabilityLabel.text = "Reachable via WWAN" 38 | 39 | default : 40 | networkReachabilityLabel.text = "Unknown" 41 | } 42 | } else { 43 | networkReachabilityLabel.text = "Unknown" 44 | } 45 | } 46 | 47 | // MARK: Overridden UIViewController Methods 48 | 49 | override public func viewDidLoad() { 50 | super.viewDidLoad() 51 | 52 | displayNetworkReachability(nil) 53 | } 54 | 55 | override public func viewWillAppear(_ animated: Bool) { 56 | super.viewWillAppear(animated) 57 | 58 | monitors.forEach { $0.startMonitoring() } 59 | } 60 | 61 | override public func viewWillDisappear(_ animated: Bool) { 62 | monitors.forEach { $0.stopMonitoring() } 63 | 64 | super.viewWillDisappear(animated) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/UIKitScreenViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIKitScreenViewController.swift 3 | // XestiMonitorsDemo-iOS 4 | // 5 | // Created by Paul Nyondo on 2018-03-23. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md). 8 | // 9 | 10 | import UIKit 11 | import XestiMonitors 12 | 13 | public class UIKitScreenViewController: UITableViewController { 14 | 15 | // MARK: Private Instance Properties 16 | 17 | @IBOutlet private weak var brightnessLevelLabel: UILabel! 18 | 19 | private lazy var screenBrightnessMonitor = ScreenBrightnessMonitor(screen: .main, 20 | queue: .main) { [unowned self] in 21 | self.displayScreenBrightness($0) 22 | } 23 | 24 | private lazy var monitors: [Monitor] = [screenBrightnessMonitor] 25 | 26 | // MARK: Private Instance Methods 27 | 28 | private func displayScreenBrightness(_ event: ScreenBrightnessMonitor.Event?) { 29 | if let event = event, 30 | case let .didChange(screen) = event { 31 | brightnessLevelLabel.text = formatPercentage(Float(screen.brightness)) 32 | } else { 33 | brightnessLevelLabel.text = formatPercentage(Float(UIScreen.main.brightness)) 34 | } 35 | } 36 | 37 | // MARK: Overridden UIViewController Methods 38 | 39 | override public func viewDidLoad() { 40 | super.viewDidLoad() 41 | 42 | displayScreenBrightness(nil) 43 | } 44 | 45 | override public func viewWillAppear(_ animated: Bool) { 46 | super.viewWillAppear(animated) 47 | 48 | monitors.forEach { $0.startMonitoring() } 49 | } 50 | 51 | override public func viewWillDisappear(_ animated: Bool) { 52 | super.viewWillDisappear(animated) 53 | 54 | monitors.forEach { $0.stopMonitoring() } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo/XestiMonitorsDemo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | platform :tvos, '9.0' 4 | 5 | use_frameworks! 6 | 7 | target 'XestiMonitorsDemo' do 8 | pod 'XestiMonitors', :path => '../../' 9 | end 10 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - XestiMonitors (2.12.0): 3 | - XestiMonitors/Core (= 2.12.0) 4 | - XestiMonitors/Core (2.12.0) 5 | 6 | DEPENDENCIES: 7 | - XestiMonitors (from `../../`) 8 | 9 | EXTERNAL SOURCES: 10 | XestiMonitors: 11 | :path: "../../" 12 | 13 | SPEC CHECKSUMS: 14 | XestiMonitors: 8a5635a8fdf6f1909220ef93894b90f0d05130d8 15 | 16 | PODFILE CHECKSUM: 1126d3f650bae5d09bc018cdd9097411c8d68f2e 17 | 18 | COCOAPODS: 1.5.3 19 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // XestiMonitorsDemo-tvOS 4 | // 5 | // Created by J. G. Pusey on 2018-01-11. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | 12 | @UIApplicationMain 13 | public class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { 14 | 15 | // MARK: Public Instance Properties 16 | 17 | public var window: UIWindow? 18 | 19 | // MARK: UIApplicationDelegate Methods 20 | 21 | public func application(_ application: UIApplication, 22 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 23 | guard 24 | let svc = self.window?.rootViewController as? UISplitViewController 25 | else { return false } 26 | 27 | svc.delegate = self 28 | svc.preferredDisplayMode = .allVisible 29 | 30 | guard 31 | let nc = svc.viewControllers[svc.viewControllers.count - 1] as? UINavigationController 32 | else { return false } 33 | 34 | nc.topViewController?.navigationItem.leftBarButtonItem = svc.displayModeButtonItem 35 | 36 | return true 37 | } 38 | 39 | public func applicationDidBecomeActive(_ application: UIApplication) { 40 | } 41 | 42 | public func applicationDidEnterBackground(_ application: UIApplication) { 43 | } 44 | 45 | public func applicationWillEnterForeground(_ application: UIApplication) { 46 | } 47 | 48 | public func applicationWillResignActive(_ application: UIApplication) { 49 | } 50 | 51 | public func applicationWillTerminate(_ application: UIApplication) { 52 | } 53 | 54 | // MARK: UISplitViewControllerDelegate Methods 55 | 56 | public func targetDisplayModeForAction(in splitViewController: UISplitViewController) -> UISplitViewControllerDisplayMode { 57 | return .allVisible 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "version" : 1, 9 | "author" : "xcode" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "version" : 1, 9 | "author" : "xcode" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "version" : 1, 9 | "author" : "xcode" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "version" : 1, 14 | "author" : "xcode" 15 | } 16 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "version" : 1, 14 | "author" : "xcode" 15 | } 16 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "version" : 1, 14 | "author" : "xcode" 15 | } 16 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - App Store.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "2320x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image Wide.imageset", 19 | "role" : "top-shelf-image-wide" 20 | }, 21 | { 22 | "size" : "1920x720", 23 | "idiom" : "tv", 24 | "filename" : "Top Shelf Image.imageset", 25 | "role" : "top-shelf-image" 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "version" : 1, 14 | "author" : "xcode" 15 | } 16 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "version" : 1, 14 | "author" : "xcode" 15 | } 16 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "11.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "landscape", 12 | "idiom" : "tv", 13 | "extent" : "full-screen", 14 | "minimum-system-version" : "9.0", 15 | "scale" : "1x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSLocationWhenInUseUsageDescription 24 | For demonstration purposes. 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | arm64 30 | 31 | UIUserInterfaceStyle 32 | Automatic 33 | 34 | 35 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/MasterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController.swift 3 | // XestiMonitorsDemo-tvOS 4 | // 5 | // Created by J. G. Pusey on 2018-01-11. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | 12 | public class MasterViewController: UITableViewController { 13 | override public func viewWillAppear(_ animated: Bool) { 14 | self.clearsSelectionOnViewWillAppear = self.splitViewController?.isCollapsed ?? false 15 | 16 | super.viewWillAppear(animated) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo/OtherViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherViewController.swift 3 | // XestiMonitorsDemo-tvOS 4 | // 5 | // Created by J. G. Pusey on 2018-01-11. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XestiMonitors 12 | 13 | public class OtherViewController: UITableViewController, UITextFieldDelegate { 14 | 15 | // MARK: Private Instance Properties 16 | 17 | @IBOutlet private weak var networkReachabilityLabel: UILabel! 18 | 19 | private lazy var networkReachabilityMonitor = NetworkReachabilityMonitor(queue: .main) { [unowned self] in 20 | self.displayNetworkReachability($0) 21 | } 22 | 23 | private lazy var monitors: [Monitor] = [networkReachabilityMonitor] 24 | 25 | // MARK: Private Instance Methods 26 | 27 | private func displayNetworkReachability(_ event: NetworkReachabilityMonitor.Event?) { 28 | if let event = event, 29 | case let .statusDidChange(status) = event { 30 | switch status { 31 | case .notReachable: 32 | networkReachabilityLabel.text = "Not reachable" 33 | 34 | case .reachableViaWiFi: 35 | networkReachabilityLabel.text = "Reachable via Wi-Fi" 36 | 37 | default : 38 | networkReachabilityLabel.text = "Unknown" 39 | } 40 | } else { 41 | networkReachabilityLabel.text = "Unknown" 42 | } 43 | } 44 | 45 | // MARK: Overridden UIViewController Methods 46 | 47 | override public func viewDidLoad() { 48 | super.viewDidLoad() 49 | 50 | displayNetworkReachability(nil) 51 | } 52 | 53 | override public func viewWillAppear(_ animated: Bool) { 54 | super.viewWillAppear(animated) 55 | 56 | monitors.forEach { $0.startMonitoring() } 57 | } 58 | 59 | override public func viewWillDisappear(_ animated: Bool) { 60 | monitors.forEach { $0.stopMonitoring() } 61 | 62 | super.viewWillDisappear(animated) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | © 2016–2018 J. G. Pusey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | 3 | // 4 | // Package.swift 5 | // XestiMonitors 6 | // 7 | // Created by J. G. Pusey on 2018-01-10. 8 | // 9 | // © 2018 J. G. Pusey (see LICENSE.md) 10 | // 11 | 12 | import PackageDescription 13 | 14 | let package = Package(name: "XestiMonitors", 15 | products: [.library(name: "XestiMonitors", 16 | targets: ["XestiMonitors"])], 17 | dependencies: [], 18 | targets: [.target(name: "XestiMonitors", 19 | dependencies: [], 20 | path: ".", 21 | exclude: ["*.h", "*.m"], 22 | sources: ["Sources/Core"])], 23 | swiftLanguageVersions: [4]) 24 | -------------------------------------------------------------------------------- /Scripts/generate_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if (( $# != 0 )); then 6 | echo "Usage: generate_docs.sh" 1>&2 7 | exit 1 8 | fi 9 | 10 | SCRIPTS_DIR=$(unset CDPATH && cd "${0%/*}" &>/dev/null && pwd) 11 | 12 | cd "$SCRIPTS_DIR"/.. 13 | 14 | VERSION=$(sed -En "/s.version[[:space:]]*=/s/^.*\\'(.*)\\'.*$/\1/gp" XestiMonitors.podspec) 15 | 16 | if git rev-parse "v$VERSION" >/dev/null 2>&1; then 17 | REF="v$VERSION" 18 | else 19 | REF="$(git rev-parse HEAD)" 20 | fi 21 | 22 | jazzy --clean \ 23 | --github-file-prefix "https://github.com/eBardX/XestiMonitors/tree/$REF" \ 24 | --module-version "$VERSION" \ 25 | --xcodebuild-arguments "-project,XestiMonitors.xcodeproj,-scheme,XestiMonitors-iOS" 26 | -------------------------------------------------------------------------------- /Scripts/uniquify_projects.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if (( $# != 0 )); then 4 | echo "Usage: uniquify_projects.sh" 1>&2 5 | exit 1 6 | fi 7 | 8 | SCRIPTS_DIR=$(unset CDPATH && cd "${0%/*}" &>/dev/null && pwd) 9 | MAIN_PROJECT=$(unset CDPATH && cd "$SCRIPTS_DIR"/../XestiMonitors.xcodeproj &>/dev/null && pwd) 10 | IOS_DEMO_PROJECT=$(unset CDPATH && cd "$SCRIPTS_DIR"/../Examples/XestiMonitorsDemo-iOS/XestiMonitorsDemo.xcodeproj &>/dev/null && pwd) 11 | IOS_PODS_PROJECT=$(unset CDPATH && cd "$SCRIPTS_DIR"/../Examples/XestiMonitorsDemo-iOS/Pods/Pods.xcodeproj &>/dev/null && pwd) 12 | TVOS_DEMO_PROJECT=$(unset CDPATH && cd "$SCRIPTS_DIR"/../Examples/XestiMonitorsDemo-tvOS/XestiMonitorsDemo.xcodeproj &>/dev/null && pwd) 13 | TVOS_PODS_PROJECT=$(unset CDPATH && cd "$SCRIPTS_DIR"/../Examples/XestiMonitorsDemo-tvOS/Pods/Pods.xcodeproj &>/dev/null && pwd) 14 | 15 | function uniquify_project() { 16 | local PROJECT_DIR=$1 17 | local PROJECT_FILE="$PROJECT_DIR"/project.pbxproj 18 | 19 | if ! xunique -c -p "$PROJECT_FILE" >/dev/null; then 20 | git add "$PROJECT_DIR"/ 21 | fi 22 | } 23 | 24 | uniquify_project "$MAIN_PROJECT" 25 | uniquify_project "$IOS_DEMO_PROJECT" 26 | uniquify_project "$IOS_PODS_PROJECT" 27 | uniquify_project "$TVOS_DEMO_PROJECT" 28 | uniquify_project "$TVOS_PODS_PROJECT" 29 | 30 | exit 0 31 | -------------------------------------------------------------------------------- /Sources/Core/Base/BaseMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2016-11-23. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | /// 11 | /// An abstract base class that simplifies the implementation of a monitor. 12 | /// 13 | open class BaseMonitor: Monitor { 14 | /// 15 | /// Cleans up the monitor so that active monitoring can stop. 16 | /// 17 | /// If monitoring is not active when the `stopMonitoring()` method is 18 | /// invoked, this method is not called. If you override this method, you 19 | /// must be sure to invoke the superclass implementation. 20 | /// 21 | open func cleanupMonitor() { 22 | } 23 | 24 | /// 25 | /// Configures the monitor so that active monitoring can start. 26 | /// 27 | /// If monitoring is already active when the `startMonitoring()` method is 28 | /// invoked, this method is not called. If you override this method, you 29 | /// must be sure to invoke the superclass implementation. 30 | /// 31 | open func configureMonitor() { 32 | } 33 | 34 | /// 35 | /// Initializes a new base monitor. 36 | /// 37 | public init() { 38 | } 39 | 40 | /// 41 | /// A Boolean value indicating whether monitoring of events specific to the 42 | /// monitor is active. 43 | /// 44 | public private(set) final var isMonitoring = false 45 | 46 | /// 47 | /// Starts active monitoring of events specific to the monitor. 48 | /// 49 | public final func startMonitoring() { 50 | if !isMonitoring { 51 | configureMonitor() 52 | isMonitoring = true 53 | } 54 | } 55 | 56 | /// 57 | /// Stops active monitoring of events specific to the monitor. 58 | /// 59 | public final func stopMonitoring() { 60 | if isMonitoring { 61 | cleanupMonitor() 62 | isMonitoring = false 63 | } 64 | } 65 | 66 | deinit { 67 | stopMonitoring() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Core/Base/Monitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Monitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2016-11-23. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | /// 11 | /// A type that monitors events. What constitutes an *event* is specific to the 12 | /// implementation. 13 | /// 14 | public protocol Monitor { 15 | /// 16 | /// A Boolean value indicating whether monitoring of events specific to the 17 | /// monitor is active. 18 | /// 19 | var isMonitoring: Bool { get } 20 | 21 | /// 22 | /// Starts active monitoring of events specific to the monitor. 23 | /// 24 | func startMonitoring() 25 | 26 | /// 27 | /// Stops active monitoring of events specific to the monitor. 28 | /// 29 | func stopMonitoring() 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Core/CoreLocation/VisitMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VisitMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-03-21. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) 11 | 12 | import CoreLocation 13 | 14 | /// 15 | /// A `VisitMonitor` instance monitors for locations that the user stops at for 16 | /// a “noteworthy” amount of time. This is considered to be a *visit*. 17 | /// 18 | /// - Note: 19 | /// An authorization status of `authorizedAlways` is required. 20 | /// 21 | public class VisitMonitor: BaseMonitor { 22 | /// 23 | /// Encapsulates changes to the device’s current location that constitute 24 | /// a visit. 25 | /// 26 | public enum Event { 27 | /// 28 | /// A visit has been determined or updated. 29 | /// 30 | case didUpdate(Info) 31 | } 32 | 33 | /// 34 | /// Encapsulates information associated with a visit monitor event. 35 | /// 36 | public enum Info { 37 | /// 38 | /// The error encountered in attempting to determine or update a visit. 39 | /// 40 | case error(Error) 41 | 42 | /// 43 | /// The latest visit data. 44 | /// 45 | case visit(CLVisit) 46 | } 47 | 48 | /// 49 | /// Initializes a new `VisitMonitor`. 50 | /// 51 | /// - Parameters: 52 | /// - queue: The operation queue on which the handler executes. 53 | /// - handler: The handler to call when a visit is determined or 54 | /// updated. 55 | /// 56 | public init(queue: OperationQueue, 57 | handler: @escaping (Event) -> Void) { 58 | self.adapter = .init() 59 | self.handler = handler 60 | self.locationManager = LocationManagerInjector.inject() 61 | self.queue = queue 62 | 63 | super.init() 64 | 65 | self.adapter.didFail = handleDidFail 66 | self.adapter.didVisit = handleDidVisit 67 | 68 | self.locationManager.delegate = self.adapter 69 | } 70 | 71 | private let adapter: LocationManagerDelegateAdapter 72 | private let handler: (Event) -> Void 73 | private let locationManager: LocationManagerProtocol 74 | private let queue: OperationQueue 75 | 76 | private func handleDidFail(_ error: Error) { 77 | handler(.didUpdate(.error(error))) 78 | } 79 | 80 | private func handleDidVisit(_ visit: CLVisit) { 81 | handler(.didUpdate(.visit(visit))) 82 | } 83 | 84 | override public func cleanupMonitor() { 85 | locationManager.stopMonitoringVisits() 86 | 87 | super.cleanupMonitor() 88 | } 89 | 90 | override public func configureMonitor() { 91 | super.configureMonitor() 92 | 93 | locationManager.startMonitoringVisits() 94 | } 95 | } 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/AccessibilityStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessibilityStatus.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-01-07. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import UIKit 13 | 14 | internal class AccessibilityStatus { 15 | 16 | // MARK: Public Instance Methods 17 | 18 | internal func darkerSystemColorsEnabled() -> Bool { 19 | return UIAccessibilityDarkerSystemColorsEnabled() 20 | } 21 | 22 | #if os(iOS) 23 | @available(iOS 10.0, *) 24 | internal func hearingDevicePairedEar() -> UIAccessibilityHearingDeviceEar { 25 | return UIAccessibilityHearingDevicePairedEar() 26 | } 27 | #endif 28 | 29 | @available(iOS 10.0, tvOS 10.0, *) 30 | internal func isAssistiveTouchRunning() -> Bool { 31 | return UIAccessibilityIsAssistiveTouchRunning() 32 | } 33 | 34 | internal func isBoldTextEnabled() -> Bool { 35 | return UIAccessibilityIsBoldTextEnabled() 36 | } 37 | 38 | internal func isClosedCaptioningEnabled() -> Bool { 39 | return UIAccessibilityIsClosedCaptioningEnabled() 40 | } 41 | 42 | internal func isGrayscaleEnabled() -> Bool { 43 | return UIAccessibilityIsGrayscaleEnabled() 44 | } 45 | 46 | internal func isGuidedAccessEnabled() -> Bool { 47 | return UIAccessibilityIsGuidedAccessEnabled() 48 | } 49 | 50 | internal func isInvertColorsEnabled() -> Bool { 51 | return UIAccessibilityIsInvertColorsEnabled() 52 | } 53 | 54 | internal func isMonoAudioEnabled() -> Bool { 55 | return UIAccessibilityIsMonoAudioEnabled() 56 | } 57 | 58 | internal func isReduceMotionEnabled() -> Bool { 59 | return UIAccessibilityIsReduceMotionEnabled() 60 | } 61 | 62 | internal func isReduceTransparencyEnabled() -> Bool { 63 | return UIAccessibilityIsReduceTransparencyEnabled() 64 | } 65 | 66 | internal func isShakeToUndoEnabled() -> Bool { 67 | return UIAccessibilityIsShakeToUndoEnabled() 68 | } 69 | 70 | internal func isSpeakScreenEnabled() -> Bool { 71 | return UIAccessibilityIsSpeakScreenEnabled() 72 | } 73 | 74 | internal func isSpeakSelectionEnabled() -> Bool { 75 | return UIAccessibilityIsSpeakSelectionEnabled() 76 | } 77 | 78 | internal func isSwitchControlRunning() -> Bool { 79 | return UIAccessibilityIsSwitchControlRunning() 80 | } 81 | 82 | internal func isVoiceOverRunning() -> Bool { 83 | return UIAccessibilityIsVoiceOverRunning() 84 | } 85 | } 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/AccessibilityStatusInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessibilityStatusInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2017-12-29. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import UIKit 13 | 14 | internal protocol AccessibilityStatusProtocol: AnyObject { 15 | func darkerSystemColorsEnabled() -> Bool 16 | 17 | #if os(iOS) 18 | @available(iOS 10.0, *) 19 | func hearingDevicePairedEar() -> UIAccessibilityHearingDeviceEar 20 | #endif 21 | 22 | @available(iOS 10.0, tvOS 10.0, *) 23 | func isAssistiveTouchRunning() -> Bool 24 | 25 | func isBoldTextEnabled() -> Bool 26 | 27 | func isClosedCaptioningEnabled() -> Bool 28 | 29 | func isGrayscaleEnabled() -> Bool 30 | 31 | func isGuidedAccessEnabled() -> Bool 32 | 33 | func isInvertColorsEnabled() -> Bool 34 | 35 | func isMonoAudioEnabled() -> Bool 36 | 37 | func isReduceMotionEnabled() -> Bool 38 | 39 | func isReduceTransparencyEnabled() -> Bool 40 | 41 | func isShakeToUndoEnabled() -> Bool 42 | 43 | func isSpeakScreenEnabled() -> Bool 44 | 45 | func isSpeakSelectionEnabled() -> Bool 46 | 47 | func isSwitchControlRunning() -> Bool 48 | 49 | func isVoiceOverRunning() -> Bool 50 | } 51 | 52 | extension AccessibilityStatus: AccessibilityStatusProtocol {} 53 | 54 | internal enum AccessibilityStatusInjector { 55 | internal static var inject: () -> AccessibilityStatusProtocol = { shared } 56 | 57 | private static let shared: AccessibilityStatusProtocol = AccessibilityStatus() 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/AltimeterInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AltimeterInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2017-12-30. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(watchOS) 11 | 12 | import CoreMotion 13 | 14 | internal protocol AltimeterProtocol: AnyObject { 15 | static func isRelativeAltitudeAvailable() -> Bool 16 | 17 | func startRelativeAltitudeUpdates(to queue: OperationQueue, 18 | withHandler handler: @escaping CMAltitudeHandler) 19 | 20 | func stopRelativeAltitudeUpdates() 21 | } 22 | 23 | extension CMAltimeter: AltimeterProtocol {} 24 | 25 | internal enum AltimeterInjector { 26 | internal static var inject: () -> AltimeterProtocol = { CMAltimeter() } 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/ApplicationInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2017-12-29. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import UIKit 13 | 14 | internal protocol ApplicationProtocol: AnyObject { 15 | var applicationState: UIApplicationState { get } 16 | 17 | #if os(iOS) 18 | var backgroundRefreshStatus: UIBackgroundRefreshStatus { get } 19 | #endif 20 | 21 | var isProtectedDataAvailable: Bool { get } 22 | 23 | var preferredContentSizeCategory: UIContentSizeCategory { get } 24 | 25 | #if os(iOS) 26 | var statusBarFrame: CGRect { get } 27 | 28 | var statusBarOrientation: UIInterfaceOrientation { get } 29 | #endif 30 | } 31 | 32 | extension UIApplication: ApplicationProtocol {} 33 | 34 | internal enum ApplicationInjector { 35 | internal static var inject: () -> ApplicationProtocol = { UIApplication.shared } 36 | } 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/DeviceInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2017-12-29. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) 11 | 12 | import UIKit 13 | 14 | internal protocol DeviceProtocol: AnyObject { 15 | var batteryLevel: Float { get } 16 | 17 | var batteryState: UIDeviceBatteryState { get } 18 | 19 | var isBatteryMonitoringEnabled: Bool { get set } 20 | 21 | var isProximityMonitoringEnabled: Bool { get set } 22 | 23 | var orientation: UIDeviceOrientation { get } 24 | 25 | var proximityState: Bool { get } 26 | 27 | func beginGeneratingDeviceOrientationNotifications() 28 | 29 | func endGeneratingDeviceOrientationNotifications() 30 | } 31 | 32 | extension UIDevice: DeviceProtocol {} 33 | 34 | internal enum DeviceInjector { 35 | internal static var inject: () -> DeviceProtocol = { UIDevice.current } 36 | } 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/FileManagerInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileManagerInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-03-14. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | 12 | internal protocol FileManagerProtocol: AnyObject { 13 | var ubiquityIdentityToken: (NSCoding & NSCopying & NSObjectProtocol)? { get } 14 | } 15 | 16 | extension FileManager: FileManagerProtocol {} 17 | 18 | internal enum FileManagerInjector { 19 | internal static var inject: () -> FileManagerProtocol = { FileManager.`default` } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/FileSystem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileSystem.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-02-21. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Darwin 11 | 12 | internal class FileSystem { 13 | 14 | // MARK: Public Instance Methods 15 | 16 | internal func close(_ fd: Int32) -> Int32 { 17 | return Darwin.close(fd) 18 | } 19 | 20 | internal func fcntl(_ fd: Int32, 21 | _ cmd: Int32, 22 | _ ptr: UnsafeMutableRawPointer) -> Int32 { 23 | return Darwin.fcntl(fd, cmd, ptr) 24 | } 25 | 26 | internal func open(_ path: UnsafePointer, 27 | _ oflag: Int32) -> Int32 { 28 | return Darwin.open(path, oflag) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/FileSystemInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileSystemInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-02-21. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | internal protocol FileSystemProtocol: AnyObject { 11 | @discardableResult 12 | func close(_ fd: Int32) -> Int32 13 | 14 | func fcntl(_ fd: Int32, 15 | _ cmd: Int32, 16 | _ ptr: UnsafeMutableRawPointer) -> Int32 17 | 18 | func open(_ path: UnsafePointer, 19 | _ oflag: Int32) -> Int32 20 | } 21 | 22 | extension FileSystem: FileSystemProtocol {} 23 | 24 | internal enum FileSystemInjector { 25 | internal static var inject: () -> FileSystemProtocol = { shared } 26 | 27 | private static let shared: FileSystemProtocol = FileSystem() 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/FileSystemObjectInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileSystemObjectInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-02-21. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Dispatch 11 | 12 | internal protocol FileSystemObjectProtocol: AnyObject { 13 | var data: DispatchSource.FileSystemEvent { get } 14 | 15 | func cancel() 16 | 17 | func resume() 18 | 19 | func setCancelHandler(qos: DispatchQoS, 20 | flags: DispatchWorkItemFlags, 21 | handler: DispatchSourceProtocol.DispatchSourceHandler?) 22 | 23 | func setEventHandler(qos: DispatchQoS, 24 | flags: DispatchWorkItemFlags, 25 | handler: DispatchSourceProtocol.DispatchSourceHandler?) 26 | } 27 | 28 | extension DispatchSource: FileSystemObjectProtocol {} 29 | 30 | internal enum FileSystemObjectInjector { 31 | // swiftlint:disable force_cast 32 | 33 | internal static var inject: (Int32, DispatchSource.FileSystemEvent, DispatchQueue?) -> FileSystemObjectProtocol = { 34 | DispatchSource.makeFileSystemObjectSource(fileDescriptor: $0, 35 | eventMask: $1, 36 | queue: $2) as! DispatchSource 37 | } 38 | 39 | // swiftlint:enable force_cast 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/MotionActivityManagerInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MotionActivityManagerInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2017-12-30. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(watchOS) 11 | 12 | import CoreMotion 13 | 14 | internal protocol MotionActivityManagerProtocol: AnyObject { 15 | static func isActivityAvailable() -> Bool 16 | 17 | func queryActivityStarting(from start: Date, 18 | to end: Date, 19 | to queue: OperationQueue, 20 | withHandler handler: @escaping CMMotionActivityQueryHandler) 21 | 22 | func startActivityUpdates(to queue: OperationQueue, 23 | withHandler handler: @escaping CMMotionActivityHandler) 24 | 25 | func stopActivityUpdates() 26 | } 27 | 28 | extension CMMotionActivityManager: MotionActivityManagerProtocol {} 29 | 30 | internal enum MotionActivityManagerInjector { 31 | internal static var inject: () -> MotionActivityManagerProtocol = { CMMotionActivityManager() } 32 | } 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/MotionManagerInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MotionManagerInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2016-12-16. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(watchOS) 11 | 12 | import CoreMotion 13 | 14 | internal protocol MotionManagerProtocol: AnyObject { 15 | var accelerometerData: CMAccelerometerData? { get } 16 | 17 | var accelerometerUpdateInterval: TimeInterval { get set } 18 | 19 | var deviceMotion: CMDeviceMotion? { get } 20 | 21 | var deviceMotionUpdateInterval: TimeInterval { get set } 22 | 23 | var gyroData: CMGyroData? { get } 24 | 25 | var gyroUpdateInterval: TimeInterval { get set } 26 | 27 | var isAccelerometerAvailable: Bool { get } 28 | 29 | var isDeviceMotionAvailable: Bool { get } 30 | 31 | var isGyroAvailable: Bool { get } 32 | 33 | var isMagnetometerAvailable: Bool { get } 34 | 35 | var magnetometerData: CMMagnetometerData? { get } 36 | 37 | var magnetometerUpdateInterval: TimeInterval { get set } 38 | 39 | func startAccelerometerUpdates(to queue: OperationQueue, 40 | withHandler handler: @escaping CMAccelerometerHandler) 41 | 42 | func startDeviceMotionUpdates(using referenceFrame: CMAttitudeReferenceFrame, 43 | to queue: OperationQueue, 44 | withHandler handler: @escaping CMDeviceMotionHandler) 45 | 46 | func startGyroUpdates(to queue: OperationQueue, 47 | withHandler handler: @escaping CMGyroHandler) 48 | 49 | func startMagnetometerUpdates(to queue: OperationQueue, 50 | withHandler handler: @escaping CMMagnetometerHandler) 51 | 52 | func stopAccelerometerUpdates() 53 | 54 | func stopDeviceMotionUpdates() 55 | 56 | func stopGyroUpdates() 57 | 58 | func stopMagnetometerUpdates() 59 | } 60 | 61 | extension CMMotionManager: MotionManagerProtocol {} 62 | 63 | internal enum MotionManagerInjector { 64 | internal static var inject: () -> MotionManagerProtocol = { shared } 65 | 66 | private static let shared: MotionManagerProtocol = CMMotionManager() 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/NetworkReachability.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkReachability.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-01-07. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(macOS) || os(tvOS) 11 | 12 | import SystemConfiguration 13 | 14 | internal class NetworkReachability { 15 | 16 | // MARK: Public Nested Types 17 | 18 | internal enum Error: Swift.Error { 19 | case creationFailure 20 | } 21 | 22 | // MARK: Public Instance Methods 23 | 24 | internal func getFlags(_ flags: UnsafeMutablePointer) -> Bool { 25 | guard 26 | let handle = handle 27 | else { return false } 28 | 29 | return SCNetworkReachabilityGetFlags(handle, 30 | flags) 31 | } 32 | 33 | internal func listen(to address: UnsafePointer) throws { 34 | self.handle = SCNetworkReachabilityCreateWithAddress(nil, 35 | address) 36 | 37 | if self.handle == nil { 38 | throw Error.creationFailure 39 | } 40 | } 41 | 42 | internal func listen(to nodename: UnsafePointer) throws { 43 | self.handle = SCNetworkReachabilityCreateWithName(nil, 44 | nodename) 45 | 46 | if self.handle == nil { 47 | throw Error.creationFailure 48 | } 49 | } 50 | 51 | @discardableResult 52 | internal func setCallback(_ callout: SystemConfiguration.SCNetworkReachabilityCallBack?, 53 | _ context: UnsafeMutablePointer?) -> Bool { 54 | guard 55 | let handle = handle 56 | else { return false } 57 | 58 | return SCNetworkReachabilitySetCallback(handle, 59 | callout, 60 | context) 61 | } 62 | 63 | @discardableResult 64 | internal func setDispatchQueue(_ queue: DispatchQueue?) -> Bool { 65 | guard 66 | let handle = handle 67 | else { return false } 68 | 69 | return SCNetworkReachabilitySetDispatchQueue(handle, 70 | queue) 71 | } 72 | 73 | // MARK: Private Instance Properties 74 | 75 | private var handle: SCNetworkReachability? 76 | } 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/NetworkReachabilityInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkReachabilityInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-01-07. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(macOS) || os(tvOS) 11 | 12 | import SystemConfiguration 13 | 14 | internal protocol NetworkReachabilityProtocol: AnyObject { 15 | func getFlags(_ flags: UnsafeMutablePointer) -> Bool 16 | 17 | func listen(to address: UnsafePointer) throws 18 | 19 | func listen(to nodename: UnsafePointer) throws 20 | 21 | @discardableResult 22 | func setCallback(_ callout: SystemConfiguration.SCNetworkReachabilityCallBack?, 23 | _ context: UnsafeMutablePointer?) -> Bool 24 | 25 | @discardableResult 26 | func setDispatchQueue(_ queue: DispatchQueue?) -> Bool 27 | } 28 | 29 | extension NetworkReachability: NetworkReachabilityProtocol {} 30 | 31 | internal enum NetworkReachabilityInjector { 32 | internal static var inject: () -> NetworkReachabilityProtocol = { shared } 33 | 34 | private static let shared: NetworkReachabilityProtocol = NetworkReachability() 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/NotificationCenterInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationCenterInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-01-05. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | 12 | internal protocol NotificationCenterProtocol: AnyObject { 13 | func addObserver(forName name: NSNotification.Name?, 14 | object obj: Any?, 15 | queue: OperationQueue?, 16 | using block: @escaping (Notification) -> Void) -> NSObjectProtocol 17 | 18 | func removeObserver(_ observer: Any) 19 | } 20 | 21 | extension NotificationCenter: NotificationCenterProtocol {} 22 | 23 | internal enum NotificationCenterInjector { 24 | internal static var inject: () -> NotificationCenterProtocol = { NotificationCenter.`default` } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/PedometerInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PedometerInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2017-12-30. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(watchOS) 11 | 12 | import CoreMotion 13 | 14 | internal protocol PedometerProtocol: AnyObject { 15 | static func isCadenceAvailable() -> Bool 16 | 17 | static func isDistanceAvailable() -> Bool 18 | 19 | static func isFloorCountingAvailable() -> Bool 20 | 21 | static func isPaceAvailable() -> Bool 22 | 23 | static func isStepCountingAvailable() -> Bool 24 | 25 | func queryPedometerData(from start: Date, 26 | to end: Date, 27 | withHandler handler: @escaping CMPedometerHandler) 28 | 29 | func startUpdates(from start: Date, 30 | withHandler handler: @escaping CMPedometerHandler) 31 | 32 | func stopUpdates() 33 | } 34 | 35 | extension CMPedometer: PedometerProtocol {} 36 | 37 | internal enum PedometerInjector { 38 | internal static var inject: () -> PedometerProtocol = { CMPedometer() } 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /Sources/Core/DependencyInjection/ProcessInfoInjection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProcessInfoInjection.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-05-13. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | 12 | internal protocol ProcessInfoProtocol: AnyObject { 13 | #if os(iOS) || os(tvOS) || os(watchOS) 14 | var isLowPowerModeEnabled: Bool { get } 15 | #endif 16 | 17 | @available(iOS 11.0, OSX 10.10.3, tvOS 11.0, watchOS 4.0, *) 18 | var thermalState: ProcessInfo.ThermalState { get } 19 | } 20 | 21 | extension ProcessInfo: ProcessInfoProtocol {} 22 | 23 | internal enum ProcessInfoInjector { 24 | internal static var inject: () -> ProcessInfoProtocol = { ProcessInfo.processInfo } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Core/Extensions/CMAcceleration+DeviceOrientation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CMAccelerometerData+DeviceOrientation.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2016-12-16. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) 11 | 12 | import CoreMotion 13 | import UIKit 14 | 15 | public extension CMAcceleration { 16 | /// 17 | /// Returns the device orientation as calculated from the 3-axis 18 | /// acceleration data. 19 | /// 20 | /// This property allows you to determine the physical orientation of 21 | /// the device from an acceleration measurement provided by an 22 | /// `AccelerometerMonitor` instance. There is one important case where 23 | /// you might choose to use this technique rather than directly monitor 24 | /// device orientation changes with an `OrientationMonitor` 25 | /// instance—when rotation is locked on the device. 26 | /// 27 | var deviceOrientation: UIDeviceOrientation { 28 | if z > 0.8 { 29 | return .faceDown 30 | } 31 | 32 | if z < -0.8 { 33 | return .faceUp 34 | } 35 | 36 | let angle = atan2(y, -x) 37 | 38 | if (angle >= -2.0) && (angle <= -1.0) { 39 | return .portrait 40 | } 41 | 42 | if (angle >= -0.5) && (angle <= 0.5) { 43 | return .landscapeLeft 44 | } 45 | 46 | if (angle >= 1.0) && (angle <= 2.0) { 47 | return .portraitUpsideDown 48 | } 49 | 50 | if (angle <= -2.5) || (angle >= 2.5) { 51 | return .landscapeRight 52 | } 53 | 54 | return .unknown 55 | } 56 | } 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /Sources/Core/Foundation/BundleClassLoadMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BundleClassLoadMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-05-20. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | 12 | /// 13 | /// A `BundleClassLoadMonitor` instance monitors a bundle for dynamic loads of 14 | /// classes. 15 | /// 16 | public class BundleClassLoadMonitor: BaseNotificationMonitor { 17 | /// 18 | /// Encapsulates dynamic loads of classes in the bundle. 19 | /// 20 | public enum Event { 21 | /// 22 | /// The bundle has dynamically loaded one or more classes. The second 23 | /// element in the associated value is an array of names of each class 24 | /// that was loaded. 25 | /// 26 | case didLoad(Bundle, [String]) 27 | } 28 | 29 | /// 30 | /// Initializes a new `BundleClassLoadMonitor`. 31 | /// 32 | /// - Parameters: 33 | /// - bundle: The bundle to monitor. 34 | /// - queue: The operation queue on which the handler executes. 35 | /// By default, the main operation queue is used. 36 | /// - handler: The handler to call when the bundle dynamically loads 37 | /// classes. 38 | /// 39 | public init(bundle: Bundle, 40 | queue: OperationQueue = .main, 41 | handler: @escaping (Event) -> Void) { 42 | self.bundle = bundle 43 | self.handler = handler 44 | 45 | super.init(queue: queue) 46 | } 47 | 48 | /// 49 | /// The bundle being monitored. 50 | /// 51 | public let bundle: Bundle 52 | 53 | private let handler: (Event) -> Void 54 | 55 | override public func addNotificationObservers() { 56 | super.addNotificationObservers() 57 | 58 | observe(Bundle.didLoadNotification, 59 | object: bundle) { [unowned self] in 60 | if let bundle = $0.object as? Bundle, 61 | let loadedClasses = $0.userInfo?[NSLoadedClasses] as? [String] { 62 | self.handler(.didLoad(bundle, loadedClasses)) 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/Core/Foundation/BundleResourceRequestMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BundleResourceRequestMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-05-20. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(tvOS) || os(watchOS) 11 | 12 | import Foundation 13 | 14 | /// 15 | /// A `BundleResourceRequestMonitor` instance monitors the system to detect if 16 | /// the amount of available disk space for on-demand resources is getting low. 17 | /// 18 | public class BundleResourceRequestMonitor: BaseNotificationMonitor { 19 | /// 20 | /// Encapsulates detection of low disk space availability. 21 | /// 22 | public enum Event { 23 | /// 24 | /// The amount of available disk space is getting low. 25 | /// 26 | case lowDiskSpace 27 | } 28 | 29 | /// 30 | /// Initializes a new `BundleResourceRequestMonitor`. 31 | /// 32 | /// - Parameters: 33 | /// - queue: The operation queue on which the handler executes. 34 | /// By default, the main operation queue is used. 35 | /// - handler: The handler to call when low disk space is detected. 36 | /// 37 | public init(queue: OperationQueue = .main, 38 | handler: @escaping (Event) -> Void) { 39 | self.handler = handler 40 | 41 | super.init(queue: queue) 42 | } 43 | 44 | private let handler: (Event) -> Void 45 | 46 | override public func addNotificationObservers() { 47 | super.addNotificationObservers() 48 | 49 | observe(.NSBundleResourceRequestLowDiskSpace) { [unowned self] _ in 50 | self.handler(.lowDiskSpace) 51 | } 52 | } 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /Sources/Core/Foundation/CalendarDayMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarDayMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by Paul Nyondo on 2018-05-28. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | 12 | /// 13 | /// A `CalendarDayMonitor` instance monitors the system for changes to the 14 | /// calendar day. 15 | /// 16 | public class CalendarDayMonitor: BaseNotificationMonitor { 17 | /// 18 | /// Encapsulates changes to the calendar day. 19 | /// 20 | public enum Event { 21 | /// 22 | /// The calendar day has changed. 23 | /// 24 | case changed 25 | } 26 | 27 | /// 28 | /// Initializes a new `CalendarDayMonitor`. 29 | /// 30 | /// - Parameters: 31 | /// - queue: The operation queue on which the handler executes. 32 | /// By default, the main operation queue is used. 33 | /// - handler: The handler to call when the calendar day changes. 34 | /// 35 | public init(queue: OperationQueue = .main, 36 | handler: @escaping (Event) -> Void) { 37 | self.handler = handler 38 | 39 | super.init(queue: queue) 40 | } 41 | 42 | private let handler: (Event) -> Void 43 | 44 | override public func addNotificationObservers() { 45 | super.addNotificationObservers() 46 | 47 | observe(.NSCalendarDayChanged) { [unowned self] _ in 48 | self.handler(.changed) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Core/Foundation/CurrentLocaleMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentLocaleMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by Paul Nyondo on 2018-06-06. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | 12 | /// 13 | /// A `CurrentLocaleMonitor` instance monitors the system for changes to the 14 | /// user’s locale. 15 | /// 16 | public class CurrentLocaleMonitor: BaseNotificationMonitor { 17 | /// 18 | /// Encapsulates changes to the user’s locale. 19 | /// 20 | public enum Event { 21 | /// 22 | /// The user’s locale has changed. 23 | /// 24 | case didChange 25 | } 26 | 27 | /// 28 | /// Initializes a new `CurrentLocaleMonitor`. 29 | /// 30 | /// - Parameters: 31 | /// - queue: The operation queue on which the handler executes. 32 | /// By default, the main operation queue is used. 33 | /// - handler: The handler to call when the user’s locale changes. 34 | /// 35 | public init(queue: OperationQueue, 36 | handler: @escaping (Event) -> Void) { 37 | self.handler = handler 38 | 39 | super.init(queue: queue) 40 | } 41 | 42 | private let handler: (Event) -> Void 43 | 44 | override public func addNotificationObservers() { 45 | super.addNotificationObservers() 46 | 47 | observe(NSLocale.currentLocaleDidChangeNotification) { [unowned self] _ in 48 | self.handler(.didChange) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Core/Foundation/PortMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PortMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-05-13. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | 12 | /// 13 | /// A `PortMonitor` instance monitors a port for changes to its validity. 14 | /// 15 | public class PortMonitor: BaseNotificationMonitor { 16 | /// 17 | /// Encapsulates changes to the validity of the port. 18 | /// 19 | public enum Event { 20 | /// 21 | /// The port has become invalid. 22 | /// 23 | case didBecomeInvalid(Port) 24 | } 25 | 26 | /// 27 | /// Initializes a new `PortMonitor`. 28 | /// 29 | /// - Parameters: 30 | /// - port: The port to monitor. 31 | /// - queue: The operation queue on which the handler executes. 32 | /// By default, the main operation queue is used. 33 | /// - handler: The handler to call when the validity of the port 34 | /// changes. 35 | /// 36 | public init(port: Port, 37 | queue: OperationQueue = .main, 38 | handler: @escaping (Event) -> Void) { 39 | self.handler = handler 40 | self.port = port 41 | 42 | super.init(queue: queue) 43 | } 44 | 45 | /// 46 | /// The port being monitored. 47 | /// 48 | public let port: Port 49 | 50 | private let handler: (Event) -> Void 51 | 52 | override public func addNotificationObservers() { 53 | super.addNotificationObservers() 54 | 55 | observe(Port.didBecomeInvalidNotification, 56 | object: port) { [unowned self] in 57 | if let port = $0.object as? Port { 58 | self.handler(.didBecomeInvalid(port)) 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Core/Foundation/ProcessInfoPowerStateMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProcessInfoPowerStateMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-05-13. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(tvOS) || os(watchOS) 11 | 12 | import Foundation 13 | 14 | /// 15 | /// A `ProcessInfoPowerStateMonitor` instance monitors the device for changes 16 | /// to its power state (Low Power Mode is enabled or disabled). 17 | /// 18 | public class ProcessInfoPowerStateMonitor: BaseNotificationMonitor { 19 | /// 20 | /// Encapsulates changes to the power state of the device. 21 | /// 22 | public enum Event { 23 | /// 24 | /// The power state of the device has changed. 25 | /// 26 | case didChange(Bool) 27 | } 28 | 29 | /// 30 | /// Initializes a new `ProcessInfoPowerStateMonitor`. 31 | /// 32 | /// - Parameters: 33 | /// - queue: The operation queue on which the handler executes. By 34 | /// default, the main operation queue is used. 35 | /// - handler: The handler to call when the power state of the device 36 | /// changes. 37 | /// 38 | public init(queue: OperationQueue = .main, 39 | handler: @escaping (Event) -> Void) { 40 | self.handler = handler 41 | self.processInfo = ProcessInfoInjector.inject() 42 | 43 | super.init(queue: queue) 44 | } 45 | 46 | /// 47 | /// A Boolean value indicating whether Lower Power Mode is enabled on the 48 | /// device. 49 | /// 50 | public var state: Bool { 51 | return processInfo.isLowPowerModeEnabled 52 | } 53 | 54 | private let handler: (Event) -> Void 55 | private let processInfo: ProcessInfoProtocol 56 | 57 | override public func addNotificationObservers() { 58 | super.addNotificationObservers() 59 | 60 | observe(.NSProcessInfoPowerStateDidChange, 61 | object: processInfo) { [unowned self] _ in 62 | self.handler(.didChange(self.processInfo.isLowPowerModeEnabled)) 63 | } 64 | } 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /Sources/Core/Foundation/ProcessInfoThermalStateMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProcessInfoThermalStateMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-05-13. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | 12 | /// 13 | /// A `ProcessInfoThermalStateMonitor` instance monitors the system for changes 14 | /// to the thermal state. 15 | /// 16 | @available(iOS 11.0, OSX 10.10.3, tvOS 11.0, watchOS 4.0, *) 17 | public class ProcessInfoThermalStateMonitor: BaseNotificationMonitor { 18 | /// 19 | /// Encapsulates changes to the thermal state. 20 | /// 21 | public enum Event { 22 | /// 23 | /// The thermal state has changed. 24 | /// 25 | case didChange(ProcessInfo.ThermalState) 26 | } 27 | 28 | /// 29 | /// Initializes a new `ProcessInfoThermalStateMonitor`. 30 | /// 31 | /// - Parameters: 32 | /// - queue: The operation queue on which the handler executes. By 33 | /// default, the main operation queue is used. 34 | /// - handler: The handler to call when the thermal state changes. 35 | /// 36 | public init(queue: OperationQueue = .main, 37 | handler: @escaping (Event) -> Void) { 38 | self.handler = handler 39 | self.processInfo = ProcessInfoInjector.inject() 40 | 41 | super.init(queue: queue) 42 | } 43 | 44 | /// 45 | /// The current thermal state. 46 | /// 47 | public var state: ProcessInfo.ThermalState { 48 | return processInfo.thermalState 49 | } 50 | 51 | private let handler: (Event) -> Void 52 | private let processInfo: ProcessInfoProtocol 53 | 54 | override public func addNotificationObservers() { 55 | super.addNotificationObservers() 56 | 57 | observe(ProcessInfo.thermalStateDidChangeNotification, 58 | object: processInfo) { [unowned self] _ in 59 | self.handler(.didChange(self.processInfo.thermalState)) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Core/Foundation/SystemClockMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemClockMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by Paul Nyondo on 2018-06-04. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | 12 | /// 13 | /// A `SystemClockMonitor` instance monitors the system for changes to the 14 | /// clock. 15 | /// 16 | public class SystemClockMonitor: BaseNotificationMonitor { 17 | /// 18 | /// Encapsulates changes to the system clock. 19 | /// 20 | public enum Event { 21 | /// 22 | /// The system clock has changed. 23 | /// 24 | case didChange 25 | } 26 | 27 | /// 28 | /// Initializes a new `SystemClockMonitor`. 29 | /// 30 | /// - Parameters: 31 | /// - queue: The operation queue on which the handler executes. 32 | /// By default, the main operation queue is used. 33 | /// - handler: The handler to call when the system clock changes. 34 | /// 35 | public init(queue: OperationQueue = .main, 36 | handler: @escaping (Event) -> Void) { 37 | self.handler = handler 38 | 39 | super.init(queue: queue) 40 | } 41 | 42 | private let handler: (Event) -> Void 43 | 44 | override public func addNotificationObservers() { 45 | super.addNotificationObservers() 46 | 47 | observe(.NSSystemClockDidChange) { [unowned self] _ in 48 | self.handler(.didChange) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Core/Foundation/SystemTimeZoneMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemTimeZoneMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by Angie Mugo on 2018-05-13. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | 12 | /// 13 | /// A `SystemTimeZoneMonitor` instance monitors the system for changes to the 14 | /// currently used time zone. 15 | /// 16 | public class SystemTimeZoneMonitor: BaseNotificationMonitor { 17 | /// 18 | /// Encapsulates changes to the system time zone. 19 | /// 20 | public enum Event { 21 | /// 22 | /// The system time zone has changed 23 | /// 24 | case didChange 25 | } 26 | 27 | /// 28 | /// Initializes a new `SystemTimeZoneMonitor`. 29 | /// 30 | /// - Parameters: 31 | /// - queue: The operation queue on which the handler executes. 32 | /// By default, the main operation queue is used. 33 | /// - handler: The handler to call when the system time zone changes. 34 | /// 35 | public init(queue: OperationQueue = .main, 36 | handler: @escaping (Event) -> Void) { 37 | self.handler = handler 38 | 39 | super.init(queue: queue) 40 | } 41 | 42 | private let handler: (Event) -> Void 43 | 44 | override public func addNotificationObservers() { 45 | super.addNotificationObservers() 46 | 47 | observe(.NSSystemTimeZoneDidChange) { [unowned self] _ in 48 | self.handler(.didChange) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Core/Foundation/URLCredentialStorageMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLCredentialStorageMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by Angie Mugo on 2018-05-29. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | 12 | /// 13 | /// A `URLCredentialStorageMonitor` instance monitors the shared URL credential 14 | /// storage object for changes to its stored credentials. 15 | /// 16 | public class URLCredentialStorageMonitor: BaseNotificationMonitor { 17 | /// 18 | /// Encapsulates changes to the credential storage. 19 | /// 20 | public enum Event { 21 | /// 22 | /// The stored credentials have changed. 23 | /// 24 | case changed(URLCredentialStorage) 25 | } 26 | 27 | /// 28 | /// Initializes a new `URLCredentialStorageMonitor`. 29 | /// 30 | /// - Parameters: 31 | /// - queue: The operation queue on which the handler executes. 32 | /// By default, the main operation queue is used. 33 | /// - handler: The handler to call when the set of stored URL 34 | /// credentials changes. 35 | /// 36 | public init(queue: OperationQueue = .main, 37 | handler: @escaping (Event) -> Void) { 38 | self.credentialStorage = .shared 39 | self.handler = handler 40 | 41 | super.init(queue: queue) 42 | } 43 | 44 | private let credentialStorage: URLCredentialStorage 45 | private let handler: (Event) -> Void 46 | 47 | override public func addNotificationObservers() { 48 | super.addNotificationObservers() 49 | 50 | observe(.NSURLCredentialStorageChanged, 51 | object: credentialStorage) { [unowned self] in 52 | if let credentialStorage = $0.object as? URLCredentialStorage { 53 | self.handler(.changed(credentialStorage)) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/Core/Foundation/UbiquityIdentityMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UbiquityIdentityMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-03-12. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | 12 | /// 13 | /// A `UbiquityIdentityMonitor` instance monitors the system for changes to the 14 | /// iCloud (”ubiquity”) identity. The iCloud identity changes when the current 15 | /// user logs into or out of an iCloud account, or enables or disables the 16 | /// syncing of documents and data. 17 | /// 18 | public class UbiquityIdentityMonitor: BaseNotificationMonitor { 19 | /// 20 | /// Encapsulates changes to the iCloud identity. 21 | /// 22 | public enum Event { 23 | /// 24 | /// The iCloud identity has changed. The associated value is `nil` if 25 | /// this change is due to the current user disabling or logging out of 26 | /// iCloud. 27 | /// 28 | case didChange(AnyObject?) 29 | } 30 | 31 | /// 32 | /// Initializes a new `UbiquityIdentityMonitor`. 33 | /// 34 | /// - Parameters: 35 | /// - queue: The operation queue on which the handler executes. By 36 | /// default, the main operation queue is used. 37 | /// - handler: The handler to call when the iCloud identity changes. 38 | /// 39 | public init(queue: OperationQueue = .main, 40 | handler: @escaping (Event) -> Void) { 41 | self.fileManager = FileManagerInjector.inject() 42 | self.handler = handler 43 | 44 | super.init(queue: queue) 45 | } 46 | 47 | /// 48 | /// An opaque token that represents the current user’s iCloud identity. The 49 | /// value of this token is `nil` if the current user has disabled or logged 50 | /// out of iCloud. 51 | /// 52 | public var token: AnyObject? { 53 | return fileManager.ubiquityIdentityToken as AnyObject? 54 | } 55 | 56 | private let fileManager: FileManagerProtocol 57 | private let handler: (Event) -> Void 58 | 59 | override public func addNotificationObservers() { 60 | super.addNotificationObservers() 61 | 62 | observe(.NSUbiquityIdentityDidChange) { [unowned self] _ in 63 | self.handler(.didChange(self.token)) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Accessibility/AccessibilityElementMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessibilityElementMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2017-01-17. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import Foundation 13 | import UIKit 14 | 15 | /// 16 | /// An `AccessibilityElementMonitor` instance monitors the system for 17 | /// changes to element focus by an assistive technology. 18 | /// 19 | public class AccessibilityElementMonitor: BaseNotificationMonitor { 20 | /// 21 | /// Encapsulates changes to element focus by an assistive technology. 22 | /// 23 | public enum Event { 24 | /// 25 | /// An assistive technology has changed element focus. 26 | /// 27 | case didFocus(Info) 28 | } 29 | 30 | /// 31 | /// Encapsulates information associated with an element focus change by 32 | /// an assistive technology. 33 | /// 34 | public struct Info { 35 | /// 36 | /// The identifier of the assistive technology. 37 | /// 38 | public let assistiveTechnology: String? 39 | 40 | /// 41 | /// The element that is now focused by the assistive technology. 42 | /// 43 | public let focusedElement: Any? 44 | 45 | /// 46 | /// The element that was previously focused by the assistive 47 | /// technology. 48 | /// 49 | public let unfocusedElement: Any? 50 | 51 | fileprivate init(_ notification: Notification) { 52 | let userInfo = notification.userInfo 53 | 54 | self.assistiveTechnology = userInfo?[UIAccessibilityAssistiveTechnologyKey] as? String 55 | self.focusedElement = userInfo?[UIAccessibilityFocusedElementKey] 56 | self.unfocusedElement = userInfo?[UIAccessibilityUnfocusedElementKey] 57 | } 58 | } 59 | 60 | /// 61 | /// Initializes a new `AccessibilityElementMonitor`. 62 | /// 63 | /// - Parameters: 64 | /// - queue: The operation queue on which the handler executes. 65 | /// By default, the main operation queue is used. 66 | /// - handler: The handler to call when an assistive technology 67 | /// changes element focus. 68 | /// 69 | public init(queue: OperationQueue = .main, 70 | handler: @escaping (Event) -> Void) { 71 | self.handler = handler 72 | 73 | super.init(queue: queue) 74 | } 75 | 76 | private let handler: (Event) -> Void 77 | 78 | override public func addNotificationObservers() { 79 | super.addNotificationObservers() 80 | 81 | observe(.UIAccessibilityElementFocused) { [unowned self] in 82 | self.handler(.didFocus(Info($0))) 83 | } 84 | } 85 | } 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Application/BackgroundRefreshMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackgroundRefreshMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2016-11-23. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) 11 | 12 | import Foundation 13 | import UIKit 14 | 15 | /// 16 | /// A `BackgroundRefreshMonitor` instance monitors the app for changes to 17 | /// its status for downloading content in the background. 18 | /// 19 | public class BackgroundRefreshMonitor: BaseNotificationMonitor { 20 | /// 21 | /// Encapsulates changes to the app’s status for downloading content in 22 | /// the background. 23 | /// 24 | public enum Event { 25 | /// 26 | /// The background refresh status has changed. 27 | /// 28 | case statusDidChange(UIBackgroundRefreshStatus) 29 | } 30 | 31 | /// 32 | /// Initializes a new `BackgroundRefreshMonitor`. 33 | /// 34 | /// - Parameters: 35 | /// - queue: The operation queue on which the handler executes. 36 | /// By default, the main operation queue is used. 37 | /// - handler: The handler to call when the app’s status for 38 | /// downloading content in the background changes. 39 | /// 40 | public init(queue: OperationQueue = .main, 41 | handler: @escaping (Event) -> Void) { 42 | self.application = ApplicationInjector.inject() 43 | self.handler = handler 44 | 45 | super.init(queue: queue) 46 | } 47 | 48 | /// 49 | /// Whether the app can be launched into the background to handle 50 | /// background behaviors. 51 | /// 52 | public var status: UIBackgroundRefreshStatus { 53 | return application.backgroundRefreshStatus 54 | } 55 | 56 | private let application: ApplicationProtocol 57 | private let handler: (Event) -> Void 58 | 59 | override public func addNotificationObservers() { 60 | super.addNotificationObservers() 61 | 62 | observe(.UIApplicationBackgroundRefreshStatusDidChange, 63 | object: application) { [unowned self] _ in 64 | self.handler(.statusDidChange(self.status)) 65 | } 66 | } 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Application/MemoryMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MemoryMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2016-11-23. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import Foundation 13 | import UIKit 14 | 15 | /// 16 | /// A `MemoryMonitor` instance monitors the app for memory warnings from 17 | /// the operating system. 18 | /// 19 | public class MemoryMonitor: BaseNotificationMonitor { 20 | /// 21 | /// Encapsulates warnings received by the app from the operating system 22 | /// about low memory availability. 23 | /// 24 | public enum Event { 25 | /// 26 | /// The app has received a memory warning. 27 | /// 28 | case didReceiveWarning 29 | } 30 | 31 | /// 32 | /// Initializes a new `MemoryMonitor`. 33 | /// 34 | /// - Parameters: 35 | /// - queue: The operation queue on which the handler executes. 36 | /// By default, the main operation queue is used. 37 | /// - handler: The handler to call when the app receives a warning 38 | /// from the operating system about low memory 39 | /// availability. 40 | /// 41 | public init(queue: OperationQueue = .main, 42 | handler: @escaping (Event) -> Void) { 43 | self.application = ApplicationInjector.inject() 44 | self.handler = handler 45 | 46 | super.init(queue: queue) 47 | } 48 | 49 | private let application: ApplicationProtocol 50 | private let handler: (Event) -> Void 51 | 52 | override public func addNotificationObservers() { 53 | super.addNotificationObservers() 54 | 55 | observe(.UIApplicationDidReceiveMemoryWarning, 56 | object: application) { [unowned self] _ in 57 | self.handler(.didReceiveWarning) 58 | } 59 | } 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Application/ScreenshotMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenshotMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2016-11-23. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import Foundation 13 | import UIKit 14 | 15 | /// 16 | /// A `ScreenshotMonitor` instance monitors the app for screenshots. 17 | /// 18 | public class ScreenshotMonitor: BaseNotificationMonitor { 19 | /// 20 | /// Encapsulates screenshots taken when the user presses the Home and 21 | /// Lock buttons. 22 | /// 23 | public enum Event { 24 | /// 25 | /// The user has taken a screenshot. 26 | /// 27 | case userDidTake 28 | } 29 | 30 | /// 31 | /// Initializes a new `ScreenshotMonitor`. 32 | /// 33 | /// - Parameters: 34 | /// - queue: The operation queue on which the handler executes. 35 | /// By default, the main operation queue is used. 36 | /// - handler: The handler to call when the user presses the Home 37 | /// and Lock buttons to take a screenshot. 38 | /// 39 | public init(queue: OperationQueue = .main, 40 | handler: @escaping (Event) -> Void) { 41 | self.application = ApplicationInjector.inject() 42 | self.handler = handler 43 | 44 | super.init(queue: queue) 45 | } 46 | 47 | private let application: ApplicationProtocol 48 | private let handler: (Event) -> Void 49 | 50 | override public func addNotificationObservers() { 51 | super.addNotificationObservers() 52 | 53 | observe(.UIApplicationUserDidTakeScreenshot, 54 | object: application) { [unowned self] _ in 55 | self.handler(.userDidTake) 56 | } 57 | } 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Application/TimeMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2016-11-23. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import Foundation 13 | import UIKit 14 | 15 | /// 16 | /// A `TimeMonitor` instance monitors the app for significant changes in 17 | /// time. 18 | /// 19 | public class TimeMonitor: BaseNotificationMonitor { 20 | /// 21 | /// Encapsulates significant changes in time. 22 | /// 23 | public enum Event { 24 | /// 25 | /// There has been a significant change in time. 26 | /// 27 | case significantChange 28 | } 29 | 30 | /// 31 | /// Initializes a new `TimeMonitor`. 32 | /// 33 | /// - Parameters: 34 | /// - queue: The operation queue on which the handler executes. 35 | /// By default, the main operation queue is used. 36 | /// - handler: The handler to call when there is a significant 37 | /// change in time. 38 | /// 39 | public init(queue: OperationQueue = .main, 40 | handler: @escaping (Event) -> Void) { 41 | self.application = ApplicationInjector.inject() 42 | self.handler = handler 43 | 44 | super.init(queue: queue) 45 | } 46 | 47 | private let application: ApplicationProtocol 48 | private let handler: (Event) -> Void 49 | 50 | override public func addNotificationObservers() { 51 | super.addNotificationObservers() 52 | 53 | observe(.UIApplicationSignificantTimeChange, 54 | object: application) { [unowned self] _ in 55 | self.handler(.significantChange) 56 | } 57 | } 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Device/OrientationMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OrientationMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2016-11-23. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) 11 | 12 | import Foundation 13 | import UIKit 14 | 15 | /// 16 | /// An `OrientationMonitor` instance monitors the device for changes to its 17 | /// physical orientation. 18 | /// 19 | public class OrientationMonitor: BaseNotificationMonitor { 20 | /// 21 | /// Encapsulates changes to the physical orientation of the device. 22 | /// 23 | public enum Event { 24 | /// 25 | /// The physical orientation of the device has changed. 26 | /// 27 | case didChange(UIDeviceOrientation) 28 | } 29 | 30 | /// 31 | /// Initializes a new `OrientationMonitor`. 32 | /// 33 | /// - Parameters: 34 | /// - queue: The operation queue on which the handler executes. 35 | /// By default, the main operation queue is used. 36 | /// - handler: The handler to call when the physical orientation 37 | /// of the device changes. 38 | /// 39 | public init(queue: OperationQueue = .main, 40 | handler: @escaping (Event) -> Void) { 41 | self.device = DeviceInjector.inject() 42 | self.handler = handler 43 | 44 | super.init(queue: queue) 45 | } 46 | 47 | /// 48 | /// The physical orientation of the device. 49 | /// 50 | public var orientation: UIDeviceOrientation { 51 | return device.orientation 52 | } 53 | 54 | private let device: DeviceProtocol 55 | private let handler: (Event) -> Void 56 | 57 | override public func addNotificationObservers() { 58 | super.addNotificationObservers() 59 | 60 | observe(.UIDeviceOrientationDidChange, 61 | object: device) { [unowned self] _ in 62 | self.handler(.didChange(self.device.orientation)) 63 | } 64 | 65 | device.beginGeneratingDeviceOrientationNotifications() 66 | } 67 | 68 | override public func removeNotificationObservers() { 69 | device.endGeneratingDeviceOrientationNotifications() 70 | 71 | super.removeNotificationObservers() 72 | } 73 | } 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Device/ProximityMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProximityMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2016-11-23. 6 | // 7 | // © 2016 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) 11 | 12 | import Foundation 13 | import UIKit 14 | 15 | /// 16 | /// A `ProximityMonitor` instance monitors the device for changes to the 17 | /// state of its proximity sensor. 18 | /// 19 | public class ProximityMonitor: BaseNotificationMonitor { 20 | /// 21 | /// Encapsulates changes to the state of the proximity sensor. 22 | /// 23 | public enum Event { 24 | /// 25 | /// The state of the proximity sensor has changed. 26 | /// 27 | case stateDidChange(Bool) 28 | } 29 | 30 | /// 31 | /// Initializes a new `ProximityMonitor`. 32 | /// 33 | /// - Parameters: 34 | /// - queue: The operation queue on which the handler executes. 35 | /// By default, the main operation queue is used. 36 | /// - handler: The handler to call when the state of the proximity 37 | /// sensor changes. 38 | /// 39 | public init(queue: OperationQueue = .main, 40 | handler: @escaping (Event) -> Void) { 41 | self.device = DeviceInjector.inject() 42 | self.handler = handler 43 | 44 | super.init(queue: queue) 45 | } 46 | 47 | /// 48 | /// A Boolean value indicating whether proximity monitoring is 49 | /// available on the device. 50 | /// 51 | public private(set) lazy var isAvailable: Bool = { 52 | let oldValue = self.device.isProximityMonitoringEnabled 53 | 54 | defer { self.device.isProximityMonitoringEnabled = oldValue } 55 | 56 | self.device.isProximityMonitoringEnabled = true 57 | 58 | return self.device.isProximityMonitoringEnabled 59 | }() 60 | 61 | /// 62 | /// A Boolean value indicating whether the proximity sensor is close to 63 | /// the user (`true`) or not (`false`). 64 | /// 65 | public var state: Bool { 66 | return device.proximityState 67 | } 68 | 69 | private let device: DeviceProtocol 70 | private let handler: (Event) -> Void 71 | 72 | override public func addNotificationObservers() { 73 | super.addNotificationObservers() 74 | 75 | observe(.UIDeviceProximityStateDidChange, 76 | object: device) { [unowned self] _ in 77 | self.handler(.stateDidChange(self.device.proximityState)) 78 | } 79 | 80 | device.isProximityMonitoringEnabled = true 81 | } 82 | 83 | override public func removeNotificationObservers() { 84 | device.isProximityMonitoringEnabled = false 85 | 86 | super.removeNotificationObservers() 87 | } 88 | } 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Other/ContentSizeCategoryMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentSizeCategoryMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by Angie Mugo on 2018-04-11. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md). 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import Foundation 13 | import UIKit 14 | 15 | /// 16 | /// A `ContentSizeCategoryMonitor` instance monitors the app for changes to its 17 | /// preferred content size category. 18 | /// 19 | public class ContentSizeCategoryMonitor: BaseNotificationMonitor { 20 | /// 21 | /// Encapsulates changes to the app’s preferred content size category. 22 | /// 23 | public enum Event { 24 | /// 25 | /// The preferred content size category has changed. 26 | /// 27 | case didChange(UIContentSizeCategory) 28 | } 29 | 30 | /// 31 | /// Initializes a new `ContentSizeCategoryMonitor`. 32 | /// 33 | /// - Parameters: 34 | /// - queue: The operation queue on which the handler executes. 35 | /// By default, the main operation queue is used. 36 | /// - handler: The handler to call when the app’s preferred content 37 | /// size category changes. 38 | /// 39 | public init(queue: OperationQueue = .main, 40 | handler: @escaping (Event) -> Void) { 41 | self.application = ApplicationInjector.inject() 42 | self.handler = handler 43 | 44 | super.init(queue: queue) 45 | } 46 | 47 | /// 48 | /// The font sizing option preferred by the user. 49 | /// 50 | public var preferred: UIContentSizeCategory { 51 | return application.preferredContentSizeCategory 52 | } 53 | 54 | private let application: ApplicationProtocol 55 | private let handler: (Event) -> Void 56 | 57 | private func extractContentSizeCategory(_ notification: Notification) -> UIContentSizeCategory? { 58 | guard 59 | let rawValue = notification.userInfo?[UIContentSizeCategoryNewValueKey] as? String 60 | else { return nil } 61 | 62 | return UIContentSizeCategory(rawValue: rawValue) 63 | } 64 | 65 | override public func addNotificationObservers() { 66 | super.addNotificationObservers() 67 | 68 | observe(.UIContentSizeCategoryDidChange, 69 | object: application) { [unowned self] in 70 | if let category = self.extractContentSizeCategory($0) { 71 | self.handler(.didChange(category)) 72 | } 73 | } 74 | } 75 | } 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Other/DocumentStateMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DocumentStateMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-02-16. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) 11 | 12 | import UIKit 13 | 14 | /// 15 | /// A `DocumentStateMonitor` instance monitors a document for changes to 16 | /// its state. 17 | /// 18 | public class DocumentStateMonitor: BaseNotificationMonitor { 19 | /// 20 | /// Encapsulates changes to the state of the document. 21 | /// 22 | public enum Event { 23 | /// 24 | /// The state of the document has changed. 25 | /// 26 | case didChange(UIDocument) 27 | } 28 | 29 | /// 30 | /// Initializes a new `DocumentStateMonitor`. 31 | /// 32 | /// - Parameters: 33 | /// - document: The document to monitor. 34 | /// - queue: The operation queue on which the handler executes. 35 | /// By default, the main operation queue is used. 36 | /// - handler: The handler to call when the state of the document 37 | /// changes. 38 | /// 39 | public init(document: UIDocument, 40 | queue: OperationQueue = .main, 41 | handler: @escaping (Event) -> Void) { 42 | self.document = document 43 | self.handler = handler 44 | 45 | super.init(queue: queue) 46 | } 47 | 48 | /// 49 | /// The document being monitored. 50 | /// 51 | public let document: UIDocument 52 | 53 | private let handler: (Event) -> Void 54 | 55 | override public func addNotificationObservers() { 56 | super.addNotificationObservers() 57 | 58 | observe(.UIDocumentStateChanged, 59 | object: document) { [unowned self] in 60 | if let document = $0.object as? UIDocument { 61 | self.handler(.didChange(document)) 62 | } 63 | } 64 | } 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Other/TableViewSelectionMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewSelectionMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by Rose Maina on 2018-04-20. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import UIKit 13 | 14 | /// 15 | /// A `TableViewSelectionMonitor` instance monitors a table view for changes to 16 | /// its selected row. 17 | /// 18 | public class TableViewSelectionMonitor: BaseNotificationMonitor { 19 | /// 20 | /// Encapsulates changes to the selected row in the table view. 21 | /// 22 | public enum Event { 23 | /// 24 | /// The selected row of the table view has changed. 25 | /// 26 | case didChange(UITableView) 27 | } 28 | 29 | /// 30 | /// Initializes a new `TableViewSelectionMonitor`. 31 | /// 32 | /// - Parameters: 33 | /// - tableView: The table view to monitor. 34 | /// - queue: The operation queue on which the handler executes. By 35 | /// default, the main operation queue is used. 36 | /// - handler: The handler to call when the selected row in the table 37 | /// view changes. 38 | /// 39 | public init(tableView: UITableView, 40 | queue: OperationQueue = .main, 41 | handler: @escaping (Event) -> Void) { 42 | self.handler = handler 43 | self.tableView = tableView 44 | 45 | super.init(queue: queue) 46 | } 47 | 48 | /// 49 | /// The table view being monitored. 50 | /// 51 | public let tableView: UITableView 52 | 53 | private let handler: (Event) -> Void 54 | 55 | override public func addNotificationObservers() { 56 | super.addNotificationObservers() 57 | 58 | observe(.UITableViewSelectionDidChange, 59 | object: tableView) { [unowned self] in 60 | if let tableView = $0.object as? UITableView { 61 | self.handler(.didChange(tableView)) 62 | } 63 | } 64 | } 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Other/ViewControllerShowDetailTargetMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerShowDetailTargetMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-04-14. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import UIKit 13 | 14 | /// 15 | /// A `ViewControllerShowDetailTargetMonitor` instance monitors the app for 16 | /// changes to a split view controller’s display mode in the view hierarchy. 17 | /// 18 | public class ViewControllerShowDetailTargetMonitor: BaseNotificationMonitor { 19 | /// 20 | /// Encapsulates changes to a split view controller’s display mode in the 21 | /// view hierarchy. 22 | /// 23 | public enum Event { 24 | /// 25 | /// A split view controller has been expanded or collapsed. The 26 | /// associated value is the view controller that caused the change. 27 | /// 28 | case didChange(UIViewController) 29 | } 30 | 31 | /// 32 | /// Initializes a new `ViewControllerShowDetailTargetMonitor`. 33 | /// 34 | /// - Parameters: 35 | /// - queue: The operation queue on which the handler executes. 36 | /// By default, the main operation queue is used. 37 | /// - handler: The handler to call when a split view controller is 38 | /// expanded or collapsed. 39 | /// 40 | public init(queue: OperationQueue = .main, 41 | handler: @escaping (Event) -> Void) { 42 | self.handler = handler 43 | 44 | super.init(queue: queue) 45 | } 46 | 47 | private let handler: (Event) -> Void 48 | 49 | override public func addNotificationObservers() { 50 | super.addNotificationObservers() 51 | 52 | observe(.UIViewControllerShowDetailTargetDidChange) { [unowned self] in 53 | if let vc = $0.object as? UIViewController { 54 | self.handler(.didChange(vc)) 55 | } 56 | } 57 | } 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Screen/ScreenBrightnessMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenBrightnessMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by Paul Nyondo on 2018-03-23. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md). 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import UIKit 13 | 14 | /// 15 | /// A `ScreenBrightnessMonitor` instance monitors a screen for changes to 16 | /// its brightness level. 17 | /// 18 | public class ScreenBrightnessMonitor: BaseNotificationMonitor { 19 | /// 20 | /// Encapsulates changes to the brightness level of the screen. 21 | /// 22 | public enum Event { 23 | /// 24 | /// The brightness level of the screen has changed. 25 | /// 26 | case didChange(UIScreen) 27 | } 28 | 29 | /// 30 | /// Initializes a new `ScreenBrightnessMonitor`. 31 | /// 32 | /// - Parameters: 33 | /// - screen: The screen to monitor. 34 | /// - queue: The operation queue on which the handler executes. 35 | /// By default, the main operation queue is used. 36 | /// - handler: The handler to call when the brightness level of 37 | /// the screen changes. 38 | /// 39 | public init(screen: UIScreen, 40 | queue: OperationQueue = .main, 41 | handler: @escaping (Event) -> Void) { 42 | self.handler = handler 43 | self.screen = screen 44 | 45 | super.init(queue: queue) 46 | } 47 | 48 | /// 49 | /// The screen being monitored. 50 | /// 51 | public let screen: UIScreen 52 | 53 | private let handler: (Event) -> Void 54 | 55 | override public func addNotificationObservers() { 56 | super.addNotificationObservers() 57 | 58 | observe(.UIScreenBrightnessDidChange, 59 | object: screen) { [unowned self] in 60 | if let screen = $0.object as? UIScreen { 61 | self.handler(.didChange(screen)) 62 | } 63 | } 64 | } 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Screen/ScreenCapturedMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenCapturedMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by Paul Nyondo on 2018-04-06. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md). 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import UIKit 13 | 14 | /// 15 | /// A `ScreenCapturedMonitor` instance monitors a screen for changes to its 16 | /// captured status. 17 | /// 18 | @available(iOS 11.0, tvOS 11.0, *) 19 | public class ScreenCapturedMonitor: BaseNotificationMonitor { 20 | /// 21 | /// Encapsulates changes to the captured status of the screen. 22 | /// 23 | public enum Event { 24 | /// 25 | /// The captured status of the screen has changed. 26 | /// 27 | case didChange(UIScreen) 28 | } 29 | 30 | /// 31 | /// Initializes a new `ScreenCapturedMonitor`. 32 | /// 33 | /// - Parameters: 34 | /// - screen: The screen to monitor. 35 | /// - queue: The operation queue on which the handler executes. 36 | /// By default, the main operation queue is used. 37 | /// - handler: The handler to call when the captured status of the 38 | /// screen changes. 39 | /// 40 | public init(screen: UIScreen, 41 | queue: OperationQueue = .main, 42 | handler: @escaping (Event) -> Void) { 43 | self.handler = handler 44 | self.screen = screen 45 | 46 | super.init(queue: queue) 47 | } 48 | 49 | /// 50 | /// The screen being monitored. 51 | /// 52 | public let screen: UIScreen 53 | 54 | private let handler: (Event) -> Void 55 | 56 | override public func addNotificationObservers() { 57 | super.addNotificationObservers() 58 | observe(.UIScreenCapturedDidChange) { [unowned self] in 59 | if let screen = $0.object as? UIScreen { 60 | self.handler(.didChange(screen)) 61 | } 62 | } 63 | } 64 | } 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Screen/ScreenModeMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenModeMonitor.swift 3 | // XestiMonitors 4 | // 5 | // Created by Paul Nyondo on 2018-03-31. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md). 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import UIKit 13 | 14 | /// 15 | /// A `ScreenModeMonitor` instance monitors a screen for changes to its 16 | /// current mode. 17 | /// 18 | public class ScreenModeMonitor: BaseNotificationMonitor { 19 | /// 20 | /// Encapsulates changes to the current mode of the screen. 21 | /// 22 | public enum Event { 23 | /// 24 | /// The current mode of the screen has changed. 25 | /// 26 | case didChange(UIScreen) 27 | } 28 | 29 | /// 30 | /// Initializes a new `ScreenModeMonitor`. 31 | /// 32 | /// - Parameters: 33 | /// - screen: The screen to monitor. 34 | /// - queue: The operation queue on which the handler executes. 35 | /// By default, the main operation queue is used. 36 | /// - handler: The handler to call when the current mode of the 37 | /// screen changes. 38 | /// 39 | public init(screen: UIScreen, 40 | queue: OperationQueue = .main, 41 | handler: @escaping (Event) -> Void) { 42 | self.handler = handler 43 | self.screen = screen 44 | 45 | super.init(queue: queue) 46 | } 47 | 48 | /// 49 | /// The screen being monitored. 50 | /// 51 | public let screen: UIScreen 52 | 53 | private let handler: (Event) -> Void 54 | 55 | override public func addNotificationObservers() { 56 | super.addNotificationObservers() 57 | 58 | observe(.UIScreenModeDidChange, 59 | object: screen) { [unowned self] in 60 | if let screen = $0.object as? UIScreen { 61 | self.handler(.didChange(screen)) 62 | } 63 | } 64 | } 65 | } 66 | #endif 67 | -------------------------------------------------------------------------------- /Sources/Core/UIKit/Text/TextInputModeMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextInputModeMonitorswift 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-04-07. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #if os(iOS) || os(tvOS) 11 | 12 | import UIKit 13 | 14 | /// 15 | /// A `TextInputModeMonitor` instance monitors the responder chain for changes 16 | /// to the current input mode. 17 | /// 18 | public class TextInputModeMonitor: BaseNotificationMonitor { 19 | /// 20 | /// Encapsulates changes to the current input mode. 21 | /// 22 | public enum Event { 23 | /// 24 | /// The current input mode has changed. 25 | /// 26 | case didChange(UITextInputMode?) 27 | } 28 | 29 | /// 30 | /// Initializes a new `TextInputModeMonitor`. 31 | /// 32 | /// - Parameters: 33 | /// - queue: The operation queue on which the handler executes. 34 | /// By default, the main operation queue is used. 35 | /// - handler: The handler to call when the current input mode 36 | /// changes. 37 | /// 38 | public init(queue: OperationQueue = .main, 39 | handler: @escaping (Event) -> Void) { 40 | self.handler = handler 41 | 42 | super.init(queue: queue) 43 | } 44 | 45 | private let handler: (Event) -> Void 46 | 47 | override public func addNotificationObservers() { 48 | super.addNotificationObservers() 49 | 50 | observe(.UITextInputCurrentInputModeDidChange) { [unowned self] in 51 | self.handler(.didChange($0.object as? UITextInputMode)) 52 | } 53 | } 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /Sources/Info-tvOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.12.1 19 | CFBundleVersion 20 | 2.12.1 21 | NSPrincipalClass 22 | 23 | UIRequiredDeviceCapabilities 24 | 25 | arm64 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.12.1 19 | CFBundleVersion 20 | 2.12.1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Sources/XestiMonitors.h: -------------------------------------------------------------------------------- 1 | // 2 | // XestiMonitors.h 3 | // XestiMonitors 4 | // 5 | // Created by J. G. Pusey on 2018-01-10. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | #import 11 | 12 | //! Project version number for XestiMonitors. 13 | FOUNDATION_EXPORT double XestiMonitorsVersionNumber; 14 | 15 | //! Project version string for XestiMonitors. 16 | FOUNDATION_EXPORT const unsigned char XestiMonitorsVersionString[]; 17 | 18 | // In this header, you should import all the public headers of your framework using statements like #import 19 | -------------------------------------------------------------------------------- /Tests/Base/BaseMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-04-15. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import XCTest 11 | @testable import XestiMonitors 12 | 13 | internal class BaseMonitorTests: XCTestCase { 14 | func testMonitor_isMonitoring_false1() { 15 | let monitor = BaseMonitor() 16 | 17 | XCTAssertFalse(monitor.isMonitoring) 18 | } 19 | 20 | func testMonitor_isMonitoring_false2() { 21 | let monitor = BaseMonitor() 22 | 23 | monitor.startMonitoring() 24 | monitor.stopMonitoring() 25 | 26 | XCTAssertFalse(monitor.isMonitoring) 27 | } 28 | 29 | func testMonitor_isMonitoring_true() { 30 | let monitor = BaseMonitor() 31 | 32 | monitor.startMonitoring() 33 | 34 | XCTAssertTrue(monitor.isMonitoring) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/Base/BaseNotificationMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseNotificationMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-04-15. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import XCTest 11 | @testable import XestiMonitors 12 | 13 | internal class BaseNotificationMonitorTests: XCTestCase { 14 | let notificationCenter = MockNotificationCenter() 15 | let notificationName = Notification.Name(rawValue: "Bogus") 16 | let notificationObject = NSObject() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | NotificationCenterInjector.inject = { self.notificationCenter } 22 | } 23 | 24 | func testMonitor_notificationFired() { 25 | let expectation = self.expectation(description: "Block called") 26 | var expectedNotification: Notification? 27 | let monitor = BaseNotificationMonitor(queue: .main) 28 | 29 | monitor.observe(notificationName, 30 | object: notificationObject) { notification in 31 | XCTAssertEqual(OperationQueue.current, .main) 32 | 33 | expectedNotification = notification 34 | expectation.fulfill() 35 | } 36 | 37 | monitor.startMonitoring() 38 | simulateNotificationFired() 39 | waitForExpectations(timeout: 1) 40 | monitor.stopMonitoring() 41 | 42 | if let notification = expectedNotification { 43 | XCTAssertEqual(notification.name, notificationName) 44 | XCTAssertEqual(notification.object as? NSObject, notificationObject) 45 | } else { 46 | XCTFail("Unexpected notification") 47 | } 48 | } 49 | 50 | private func simulateNotificationFired() { 51 | notificationCenter.post(name: notificationName, 52 | object: notificationObject) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/CoreLocation/VisitMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VisitMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-03-22. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import CoreLocation 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class VisitMonitorTests: XCTestCase { 15 | let locationManager = MockLocationManager() 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | LocationManagerInjector.inject = { self.locationManager } 21 | } 22 | 23 | func testMonitor_error() { 24 | let expectation = self.expectation(description: "Handler called") 25 | let expectedError = makeError() 26 | var expectedEvent: VisitMonitor.Event? 27 | let monitor = VisitMonitor(queue: .main) { event in 28 | XCTAssertEqual(OperationQueue.current, .main) 29 | 30 | expectedEvent = event 31 | expectation.fulfill() 32 | } 33 | 34 | monitor.startMonitoring() 35 | locationManager.updateVisit(error: expectedError) 36 | waitForExpectations(timeout: 1) 37 | monitor.stopMonitoring() 38 | 39 | if let event = expectedEvent, 40 | case let .didUpdate(info) = event, 41 | case let .error(error) = info { 42 | XCTAssertEqual(error as NSError, expectedError) 43 | } else { 44 | XCTFail("Unexpected event") 45 | } 46 | } 47 | 48 | func testMonitor_visit() { 49 | let expectation = self.expectation(description: "Handler called") 50 | let expectedVisit = CLVisit() 51 | var expectedEvent: VisitMonitor.Event? 52 | let monitor = VisitMonitor(queue: .main) { event in 53 | XCTAssertEqual(OperationQueue.current, .main) 54 | 55 | expectedEvent = event 56 | expectation.fulfill() 57 | } 58 | 59 | monitor.startMonitoring() 60 | locationManager.updateVisit(expectedVisit) 61 | waitForExpectations(timeout: 1) 62 | monitor.stopMonitoring() 63 | 64 | if let event = expectedEvent, 65 | case let .didUpdate(info) = event, 66 | case let .visit(visit) = info { 67 | XCTAssertEqual(visit, expectedVisit) 68 | } else { 69 | XCTFail("Unexpected event") 70 | } 71 | } 72 | 73 | private func makeError() -> NSError { 74 | return NSError(domain: "CLErrorDomain", 75 | code: CLError.Code.network.rawValue) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Tests/Foundation/BundleResourceRequestMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BundleResourceRequestMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-05-20. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md). 8 | // 9 | 10 | import XCTest 11 | @testable import XestiMonitors 12 | 13 | internal class BundleResourceRequestMonitorTests: XCTestCase { 14 | let notificationCenter = MockNotificationCenter() 15 | 16 | override func setUp() { 17 | super.setUp() 18 | 19 | NotificationCenterInjector.inject = { self.notificationCenter } 20 | } 21 | 22 | func testMonitor_didLoad() { 23 | let expectation = self.expectation(description: "Handler called") 24 | var expectedEvent: BundleResourceRequestMonitor.Event? 25 | let monitor = BundleResourceRequestMonitor(queue: .main) { event in 26 | XCTAssertEqual(OperationQueue.current, .main) 27 | 28 | expectedEvent = event 29 | expectation.fulfill() 30 | } 31 | 32 | monitor.startMonitoring() 33 | simulateLoadDiskSpace() 34 | waitForExpectations(timeout: 1) 35 | monitor.stopMonitoring() 36 | 37 | if let event = expectedEvent { 38 | XCTAssertEqual(event, .lowDiskSpace) 39 | } else { 40 | XCTFail("Unexpected event") 41 | } 42 | } 43 | 44 | private func simulateLoadDiskSpace() { 45 | notificationCenter.post(name: .NSBundleResourceRequestLowDiskSpace, 46 | object: nil) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/Foundation/CalendarDayMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarDayMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by Paul Nyondo on 2018-05-28. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class CalenderDayMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | NotificationCenterInjector.inject = { self.notificationCenter } 21 | } 22 | 23 | func testMonitor_changed() { 24 | let expectation = self.expectation(description: "Handler called") 25 | var expectedEvent: CalendarDayMonitor.Event? 26 | let monitor = CalendarDayMonitor(queue: .main) { event in 27 | XCTAssertEqual(OperationQueue.current, .main) 28 | 29 | expectedEvent = event 30 | expectation.fulfill() 31 | } 32 | 33 | monitor.startMonitoring() 34 | simulateChanged() 35 | waitForExpectations(timeout: 1) 36 | monitor.stopMonitoring() 37 | 38 | if let event = expectedEvent { 39 | XCTAssertEqual(.changed, event) 40 | } else { 41 | XCTFail("Unexpected event") 42 | } 43 | } 44 | 45 | func simulateChanged() { 46 | notificationCenter.post(name: .NSCalendarDayChanged, object: nil) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/Foundation/CurrentLocaleMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentLocaleMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by Paul Nyondo on 2018-05-28. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class CurrentLocaleMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | NotificationCenterInjector.inject = { self.notificationCenter } 21 | } 22 | 23 | func testMonitor_didChange() { 24 | let expectation = self.expectation(description: "Handler called") 25 | var expectedEvent: CurrentLocaleMonitor.Event? 26 | let monitor = CurrentLocaleMonitor(queue: .main) { event in 27 | XCTAssertEqual(OperationQueue.current, .main) 28 | 29 | expectedEvent = event 30 | expectation.fulfill() 31 | } 32 | 33 | monitor.startMonitoring() 34 | simulateDidChange() 35 | waitForExpectations(timeout: 1) 36 | monitor.stopMonitoring() 37 | 38 | if let event = expectedEvent { 39 | XCTAssertEqual(.didChange, event) 40 | } else { 41 | XCTFail("Unexpected event") 42 | } 43 | } 44 | 45 | private func simulateDidChange() { 46 | notificationCenter.post(name: NSLocale.currentLocaleDidChangeNotification, 47 | object: nil) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/Foundation/PortMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PortMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-05-13. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class PortMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | let port = Port() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | NotificationCenterInjector.inject = { self.notificationCenter } 22 | } 23 | 24 | func testMonitor_stateDidChange() { 25 | let expectation = self.expectation(description: "Handler called") 26 | var expectedEvent: PortMonitor.Event? 27 | let monitor = PortMonitor(port: port, 28 | queue: .main) { event in 29 | XCTAssertEqual(OperationQueue.current, .main) 30 | 31 | expectedEvent = event 32 | expectation.fulfill() 33 | } 34 | 35 | monitor.startMonitoring() 36 | simulateDidBecomeInvalid() 37 | waitForExpectations(timeout: 1) 38 | monitor.stopMonitoring() 39 | 40 | if let event = expectedEvent, 41 | case let .didBecomeInvalid(test) = event { 42 | XCTAssertEqual(test, port) 43 | } else { 44 | XCTFail("Unexpected event") 45 | } 46 | } 47 | 48 | private func simulateDidBecomeInvalid() { 49 | notificationCenter.post(name: Port.didBecomeInvalidNotification, 50 | object: port) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/Foundation/ProcessInfoPowerStateMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProcessInfoPowerStateMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-05-13. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class ProcessInfoPowerStateMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | let processInfo = MockProcessInfo() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | NotificationCenterInjector.inject = { self.notificationCenter } 22 | 23 | ProcessInfoInjector.inject = { self.processInfo } 24 | 25 | processInfo.isLowPowerModeEnabled = false 26 | } 27 | 28 | func testMonitor_didChange() { 29 | let expectation = self.expectation(description: "Handler called") 30 | let expectedState: Bool = true 31 | var expectedEvent: ProcessInfoPowerStateMonitor.Event? 32 | let monitor = ProcessInfoPowerStateMonitor(queue: .main) { event in 33 | XCTAssertEqual(OperationQueue.current, .main) 34 | 35 | expectedEvent = event 36 | expectation.fulfill() 37 | } 38 | 39 | monitor.startMonitoring() 40 | simulateDidChange(to: expectedState) 41 | waitForExpectations(timeout: 1) 42 | monitor.stopMonitoring() 43 | 44 | if let event = expectedEvent, 45 | case let .didChange(test) = event { 46 | XCTAssertEqual(test, expectedState) 47 | } else { 48 | XCTFail("Unexpected event") 49 | } 50 | } 51 | 52 | func testState() { 53 | let expectedState: Bool = true 54 | let monitor = ProcessInfoPowerStateMonitor(queue: .main) { _ in 55 | XCTAssertEqual(OperationQueue.current, .main) 56 | } 57 | 58 | simulateDidChange(to: expectedState) 59 | 60 | XCTAssertEqual(monitor.state, expectedState) 61 | } 62 | 63 | private func simulateDidChange(to state: Bool) { 64 | processInfo.isLowPowerModeEnabled = state 65 | 66 | notificationCenter.post(name: .NSProcessInfoPowerStateDidChange, 67 | object: processInfo) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/Foundation/ProcessInfoThermalStateMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProcessInfoThermalStateMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-05-13. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class ProcessInfoThermalStateMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | let processInfo = MockProcessInfo() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | NotificationCenterInjector.inject = { self.notificationCenter } 22 | 23 | ProcessInfoInjector.inject = { self.processInfo } 24 | 25 | processInfo.rawThermalState = 0 26 | } 27 | 28 | func testMonitor_didChange() { 29 | if #available(iOS 11.0, OSX 10.10.3, tvOS 11.0, *) { 30 | let expectation = self.expectation(description: "Handler called") 31 | let expectedState: ProcessInfo.ThermalState = .serious 32 | var expectedEvent: ProcessInfoThermalStateMonitor.Event? 33 | let monitor = ProcessInfoThermalStateMonitor(queue: .main) { event in 34 | XCTAssertEqual(OperationQueue.current, .main) 35 | 36 | expectedEvent = event 37 | expectation.fulfill() 38 | } 39 | 40 | monitor.startMonitoring() 41 | simulateDidChange(to: expectedState) 42 | waitForExpectations(timeout: 1) 43 | monitor.stopMonitoring() 44 | 45 | if let event = expectedEvent, 46 | case let .didChange(test) = event { 47 | XCTAssertEqual(test, expectedState) 48 | } else { 49 | XCTFail("Unexpected event") 50 | } 51 | } 52 | } 53 | 54 | func testState() { 55 | if #available(iOS 11.0, OSX 10.10.3, tvOS 11.0, *) { 56 | let expectedState: ProcessInfo.ThermalState = .critical 57 | let monitor = ProcessInfoThermalStateMonitor(queue: .main) { _ in 58 | XCTAssertEqual(OperationQueue.current, .main) 59 | } 60 | 61 | simulateDidChange(to: expectedState) 62 | 63 | XCTAssertEqual(monitor.state, expectedState) 64 | } 65 | } 66 | 67 | @available(iOS 11.0, OSX 10.10.3, tvOS 11.0, *) 68 | private func simulateDidChange(to state: ProcessInfo.ThermalState) { 69 | processInfo.rawThermalState = state.rawValue 70 | 71 | notificationCenter.post(name: ProcessInfo.thermalStateDidChangeNotification, 72 | object: processInfo) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Tests/Foundation/SystemClockMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemClockMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by Paul Nyondo on 2018-06-24. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class SystemClockMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | NotificationCenterInjector.inject = { self.notificationCenter } 21 | } 22 | 23 | func testMonitor_didChange() { 24 | let expectation = self.expectation(description: "Handler called") 25 | var expectedEvent: SystemClockMonitor.Event? 26 | let monitor = SystemClockMonitor(queue: .main) { event in 27 | XCTAssertEqual(OperationQueue.current, .main) 28 | 29 | expectedEvent = event 30 | expectation.fulfill() 31 | } 32 | 33 | monitor.startMonitoring() 34 | simulateDidChange() 35 | waitForExpectations(timeout: 1) 36 | monitor.stopMonitoring() 37 | 38 | if let event = expectedEvent { 39 | XCTAssertEqual(.didChange, event) 40 | } else { 41 | XCTFail("Unexpected event") 42 | } 43 | } 44 | 45 | func simulateDidChange() { 46 | notificationCenter.post(name: .NSSystemClockDidChange, 47 | object: nil) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/Foundation/SystemTimeZoneMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemTimeZoneMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by Angie Mugo on 2018-05-13. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class SystemTimeZoneMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | NotificationCenterInjector.inject = { self.notificationCenter } 21 | } 22 | 23 | func testMonitor_didChange() { 24 | let expectation = self.expectation(description: "Handler called") 25 | var expectedEvent: SystemTimeZoneMonitor.Event? 26 | let monitor = SystemTimeZoneMonitor(queue: .main) { event in 27 | XCTAssertEqual(OperationQueue.current, .main) 28 | 29 | expectedEvent = event 30 | expectation.fulfill() 31 | } 32 | 33 | monitor.startMonitoring() 34 | simulateDidChange() 35 | waitForExpectations(timeout: 1) 36 | monitor.stopMonitoring() 37 | 38 | if let event = expectedEvent { 39 | XCTAssertEqual(.didChange, event) 40 | } else { 41 | XCTFail("Unexpected event") 42 | } 43 | } 44 | 45 | func simulateDidChange() { 46 | notificationCenter.post(name: .NSSystemTimeZoneDidChange, 47 | object: nil) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/Foundation/URLCredentialStorageMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLCredentialStorageMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by Angie Mugo on 2018-05-29. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import XCTest 11 | @testable import XestiMonitors 12 | 13 | internal class URLCredentialStorageMonitorTests: XCTestCase { 14 | let notificationCenter = MockNotificationCenter() 15 | let credentialStorage = URLCredentialStorage.shared 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | NotificationCenterInjector.inject = { self.notificationCenter } 21 | } 22 | 23 | func testMonitor_changed() { 24 | let expectation = self.expectation(description: "Handler called") 25 | var expectedEvent: URLCredentialStorageMonitor.Event? 26 | let monitor = URLCredentialStorageMonitor(queue: .main) { event in 27 | XCTAssertEqual(OperationQueue.current, .main) 28 | 29 | expectedEvent = event 30 | expectation.fulfill() 31 | } 32 | 33 | monitor.startMonitoring() 34 | simulateChanged() 35 | waitForExpectations(timeout: 1) 36 | monitor.stopMonitoring() 37 | 38 | if let event = expectedEvent, 39 | case let .changed(test) = event { 40 | XCTAssertEqual(test, credentialStorage) 41 | } else { 42 | XCTFail("Unexpected event") 43 | } 44 | } 45 | 46 | func simulateChanged() { 47 | notificationCenter.post(name: .NSURLCredentialStorageChanged, 48 | object: credentialStorage) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Tests/Foundation/UbiquityIdentityMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UbiquityIdentityMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-03-14. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class UbiquityIdentityMonitorTests: XCTestCase { 15 | let fileManager = MockFileManager() 16 | let notificationCenter = MockNotificationCenter() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | FileManagerInjector.inject = { self.fileManager } 22 | 23 | NotificationCenterInjector.inject = { self.notificationCenter } 24 | } 25 | 26 | func testMonitor_didChange_nil() { 27 | let expectation = self.expectation(description: "Handler called") 28 | var expectedEvent: UbiquityIdentityMonitor.Event? 29 | let monitor = UbiquityIdentityMonitor(queue: .main) { event in 30 | XCTAssertEqual(OperationQueue.current, .main) 31 | 32 | expectedEvent = event 33 | expectation.fulfill() 34 | } 35 | 36 | monitor.startMonitoring() 37 | simulateDidChange(to: nil) 38 | waitForExpectations(timeout: 1) 39 | monitor.stopMonitoring() 40 | 41 | if let event = expectedEvent, 42 | case let .didChange(token) = event { 43 | XCTAssertNil(token) 44 | } else { 45 | XCTFail("Unexpected event") 46 | } 47 | } 48 | 49 | func testMonitor_didChange_nonNil() { 50 | let expectation = self.expectation(description: "Handler called") 51 | let expectedToken = "bogus" 52 | var expectedEvent: UbiquityIdentityMonitor.Event? 53 | let monitor = UbiquityIdentityMonitor(queue: .main) { event in 54 | XCTAssertEqual(OperationQueue.current, .main) 55 | 56 | expectedEvent = event 57 | expectation.fulfill() 58 | } 59 | 60 | monitor.startMonitoring() 61 | simulateDidChange(to: expectedToken as AnyObject) 62 | waitForExpectations(timeout: 1) 63 | monitor.stopMonitoring() 64 | 65 | if let event = expectedEvent, 66 | case let .didChange(test) = event, 67 | let actualToken = test as? String { 68 | XCTAssertEqual(actualToken, expectedToken) 69 | } else { 70 | XCTFail("Unexpected event") 71 | } 72 | } 73 | 74 | private func simulateDidChange(to token: AnyObject?) { 75 | fileManager.ubiquityIdentityToken = token as? (NSCoding & NSCopying & NSObjectProtocol) 76 | 77 | notificationCenter.post(name: .NSUbiquityIdentityDidChange, 78 | object: nil) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Tests/Info-tvOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | UIRequiredDeviceCapabilities 22 | 23 | arm64 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/Miscellaneous/CMAccelerationExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CMAccelerationExtensionsTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-01-05. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import CoreMotion 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class CMAccelerationExtensionsTests: XCTestCase { 15 | func testDeviceOrientation() { 16 | let accelerations: [(CMAcceleration, UIDeviceOrientation)] = [ 17 | (.init(x: 0.094, y: -0.401, z: 0.961), .faceDown), 18 | (.init(x: 0.014, y: -0.545, z: -0.872), .faceUp), 19 | (.init(x: -0.672, y: -0.007, z: -0.686), .landscapeLeft), 20 | (.init(x: 0.627, y: -0.029, z: -0.775), .landscapeRight), 21 | (.init(x: 0.017, y: -0.911, z: -0.565), .portrait), 22 | (.init(x: -0.058, y: 0.926, z: -0.352), .portraitUpsideDown), 23 | (.init(x: -0.722, y: -0.644, z: -0.409), .unknown)] 24 | 25 | accelerations.forEach { 26 | XCTAssertEqual($0.deviceOrientation, $1) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/Mock/MockAltimeter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockAltimeter.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-01-01. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import CoreMotion 11 | @testable import XestiMonitors 12 | 13 | internal class MockAltimeter: AltimeterProtocol { 14 | static func isRelativeAltitudeAvailable() -> Bool { 15 | return altimeterAvailable 16 | } 17 | 18 | func startRelativeAltitudeUpdates(to queue: OperationQueue, 19 | withHandler handler: @escaping CMAltitudeHandler) { 20 | altimeterHandler = handler 21 | } 22 | 23 | func stopRelativeAltitudeUpdates() { 24 | altimeterHandler = nil 25 | } 26 | 27 | private static var altimeterAvailable = false 28 | 29 | private var altimeterHandler: CMAltitudeHandler? 30 | 31 | // MARK: - 32 | 33 | func updateAltimeter(available: Bool) { 34 | type(of: self).altimeterAvailable = available 35 | } 36 | 37 | func updateAltimeter(data: CMAltitudeData?) { 38 | altimeterHandler?(data, nil) 39 | } 40 | 41 | func updateAltimeter(error: Error) { 42 | altimeterHandler?(nil, error) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/Mock/MockApplication.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockApplication.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2017-12-27. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | @testable import XestiMonitors 12 | 13 | internal class MockApplication: ApplicationProtocol { 14 | init() { 15 | self.applicationState = .inactive 16 | #if os(iOS) 17 | self.backgroundRefreshStatus = .restricted 18 | #endif 19 | self.isProtectedDataAvailable = false 20 | if #available(iOS 10.0, tvOS 10.0, *) { 21 | self.preferredContentSizeCategory = .unspecified 22 | } else { 23 | self.preferredContentSizeCategory = .medium 24 | } 25 | #if os(iOS) 26 | self.statusBarFrame = .zero 27 | self.statusBarOrientation = .unknown 28 | #endif 29 | } 30 | 31 | var applicationState: UIApplicationState 32 | #if os(iOS) 33 | var backgroundRefreshStatus: UIBackgroundRefreshStatus 34 | #endif 35 | var isProtectedDataAvailable: Bool 36 | var preferredContentSizeCategory: UIContentSizeCategory 37 | #if os(iOS) 38 | var statusBarFrame: CGRect 39 | var statusBarOrientation: UIInterfaceOrientation 40 | #endif 41 | } 42 | -------------------------------------------------------------------------------- /Tests/Mock/MockDevice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockDevice.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2017-12-30. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | @testable import XestiMonitors 12 | 13 | internal class MockDevice: DeviceProtocol { 14 | init() { 15 | self.batteryLevel = 0 16 | self.batteryState = .unknown 17 | self.isBatteryMonitoringEnabled = false 18 | self.isProximityMonitoringEnabled = false 19 | self.orientation = .unknown 20 | self.proximityState = false 21 | } 22 | 23 | var batteryLevel: Float 24 | var batteryState: UIDeviceBatteryState 25 | var isBatteryMonitoringEnabled: Bool 26 | var isProximityMonitoringEnabled: Bool 27 | var orientation: UIDeviceOrientation 28 | var proximityState: Bool 29 | 30 | func beginGeneratingDeviceOrientationNotifications() { 31 | } 32 | 33 | func endGeneratingDeviceOrientationNotifications() { 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/Mock/MockFileManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockFileManager.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-03-14. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | @testable import XestiMonitors 12 | 13 | internal class MockFileManager: FileManagerProtocol { 14 | var ubiquityIdentityToken: (NSCoding & NSCopying & NSObjectProtocol)? 15 | } 16 | -------------------------------------------------------------------------------- /Tests/Mock/MockFileSystem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockFileSystem.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-02-21. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | @testable import XestiMonitors 12 | 13 | internal class MockFileSystem: FileSystemProtocol { 14 | init() { 15 | self.fileDescriptor = -1 16 | self.filePath = nil 17 | } 18 | 19 | func close(_ fd: Int32) -> Int32 { 20 | guard 21 | fd == fileDescriptor, 22 | filePath != nil 23 | else { return -1 } 24 | 25 | fileDescriptor = -1 26 | filePath = nil 27 | 28 | return 0 29 | } 30 | 31 | func fcntl(_ fd: Int32, 32 | _ cmd: Int32, 33 | _ ptr: UnsafeMutableRawPointer) -> Int32 { 34 | guard 35 | fd == fileDescriptor, 36 | cmd == F_GETPATH, 37 | let path = filePath 38 | else { return -1 } 39 | 40 | #if swift(>=4.1) 41 | ptr.copyMemory(from: path, 42 | byteCount: String(cString: path).count) 43 | #else 44 | ptr.copyBytes(from: path, 45 | count: String(cString: path).count) 46 | #endif 47 | 48 | return 0 49 | } 50 | 51 | func open(_ path: UnsafePointer, 52 | _ oflag: Int32) -> Int32 { 53 | guard 54 | fileDescriptor < 0, 55 | filePath == nil 56 | else { return -1 } 57 | 58 | fileDescriptor = Int32(arc4random_uniform(1_000)) 59 | filePath = path 60 | 61 | return fileDescriptor 62 | } 63 | 64 | // MARK: - 65 | 66 | func rename(to path: String) { 67 | filePath = (path as NSString).fileSystemRepresentation 68 | } 69 | 70 | private var fileDescriptor: Int32 71 | private var filePath: UnsafePointer? 72 | } 73 | -------------------------------------------------------------------------------- /Tests/Mock/MockFileSystemObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockFileSystemObject.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-02-21. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | @testable import XestiMonitors 12 | 13 | internal class MockFileSystemObject: FileSystemObjectProtocol { 14 | init(fileDescriptor: Int32, 15 | eventMask: DispatchSource.FileSystemEvent, 16 | queue: DispatchQueue?) { 17 | self.data = [] 18 | self.handle = fileDescriptor 19 | self.mask = eventMask 20 | self.queue = queue ?? .global() 21 | } 22 | 23 | private(set) var data: DispatchSource.FileSystemEvent 24 | 25 | func cancel() { 26 | if let handler = cancelHandler { 27 | queue.async(execute: handler) 28 | } 29 | } 30 | 31 | func resume() { 32 | } 33 | 34 | func setCancelHandler(qos: DispatchQoS, 35 | flags: DispatchWorkItemFlags, 36 | handler: DispatchSourceProtocol.DispatchSourceHandler?) { 37 | cancelHandler = handler 38 | } 39 | 40 | func setEventHandler(qos: DispatchQoS, 41 | flags: DispatchWorkItemFlags, 42 | handler: DispatchSourceProtocol.DispatchSourceHandler?) { 43 | eventHandler = handler 44 | } 45 | 46 | // MARK: - 47 | 48 | func updateData(for eventMask: DispatchSource.FileSystemEvent) { 49 | data = mask.intersection(eventMask) 50 | 51 | if !data.isEmpty, 52 | let handler = eventHandler { 53 | queue.async(execute: handler) 54 | } 55 | } 56 | 57 | private let queue: DispatchQueue 58 | 59 | private var cancelHandler: DispatchSourceProtocol.DispatchSourceHandler? 60 | private var eventHandler: DispatchSourceProtocol.DispatchSourceHandler? 61 | private var handle: Int32 62 | private var mask: DispatchSource.FileSystemEvent 63 | } 64 | -------------------------------------------------------------------------------- /Tests/Mock/MockMotionActivityManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockMotionActivityManager.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-01-01. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import CoreMotion 11 | @testable import XestiMonitors 12 | 13 | internal class MockMotionActivityManager: MotionActivityManagerProtocol { 14 | static func isActivityAvailable() -> Bool { 15 | return motionActivityAvailable 16 | } 17 | 18 | func queryActivityStarting(from start: Date, 19 | to end: Date, 20 | to queue: OperationQueue, 21 | withHandler handler: @escaping CMMotionActivityQueryHandler) { 22 | motionActivityQueryHandler = handler 23 | } 24 | 25 | func startActivityUpdates(to queue: OperationQueue, 26 | withHandler handler: @escaping CMMotionActivityHandler) { 27 | motionActivityHandler = handler 28 | } 29 | 30 | func stopActivityUpdates() { 31 | motionActivityHandler = nil 32 | } 33 | 34 | private static var motionActivityAvailable = false 35 | 36 | private var motionActivityHandler: CMMotionActivityHandler? 37 | private var motionActivityQueryHandler: CMMotionActivityQueryHandler? 38 | 39 | // MARK: - 40 | 41 | func updateMotionActivity(available: Bool) { 42 | type(of: self).motionActivityAvailable = available 43 | } 44 | 45 | func updateMotionActivity(data: CMMotionActivity?) { 46 | motionActivityHandler?(data) 47 | } 48 | 49 | func updateMotionActivity(queryData: [CMMotionActivity]?) { 50 | if let handler = motionActivityQueryHandler { 51 | motionActivityQueryHandler = nil 52 | handler(queryData, nil) 53 | } 54 | } 55 | 56 | func updateMotionActivity(queryError: Error) { 57 | if let handler = motionActivityQueryHandler { 58 | motionActivityQueryHandler = nil 59 | handler(nil, queryError) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Tests/Mock/MockNetworkReachability.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockNetworkReachability.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-01-06. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import SystemConfiguration 11 | @testable import XestiMonitors 12 | 13 | internal class MockNetworkReachability: NetworkReachabilityProtocol { 14 | func getFlags(_ flags: UnsafeMutablePointer) -> Bool { 15 | guard 16 | let tmpFlags = self.flags 17 | else { return false } 18 | 19 | flags.pointee = tmpFlags 20 | 21 | return true 22 | } 23 | 24 | func listen(to address: UnsafePointer) throws { 25 | self.handle = SCNetworkReachabilityCreateWithAddress(nil, 26 | address) 27 | } 28 | 29 | func listen(to nodename: UnsafePointer) throws { 30 | self.handle = SCNetworkReachabilityCreateWithName(nil, 31 | nodename) 32 | } 33 | 34 | func setCallback(_ callout: SCNetworkReachabilityCallBack?, 35 | _ context: UnsafeMutablePointer?) -> Bool { 36 | self.callout = callout 37 | 38 | if let info = context?.pointee.info { 39 | self.monitor = Unmanaged.fromOpaque(info).takeUnretainedValue() 40 | } 41 | 42 | return true 43 | } 44 | 45 | func setDispatchQueue(_ queue: DispatchQueue?) -> Bool { 46 | return true 47 | } 48 | 49 | // MARK: - 50 | 51 | func clearFlags() { 52 | self.flags = [] 53 | } 54 | 55 | func updateFlags(_ flags: SCNetworkReachabilityFlags?) { 56 | self.flags = flags 57 | 58 | guard 59 | let callout = self.callout, 60 | let flags = self.flags, 61 | let handle = self.handle 62 | else { return } 63 | 64 | let info: UnsafeMutableRawPointer? 65 | 66 | if let monitor = monitor { 67 | info = Unmanaged.passUnretained(monitor).toOpaque() 68 | } else { 69 | info = nil 70 | } 71 | 72 | callout(handle, flags, info) 73 | } 74 | 75 | private var callout: SCNetworkReachabilityCallBack? 76 | private var flags: SCNetworkReachabilityFlags? 77 | private var handle: SCNetworkReachability? 78 | private weak var monitor: NetworkReachabilityMonitor? 79 | } 80 | -------------------------------------------------------------------------------- /Tests/Mock/MockNotificationCenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockNotificationCenter.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2017-12-27. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | @testable import XestiMonitors 12 | 13 | internal class MockNotificationCenter: NotificationCenterProtocol { 14 | class MockObserver { 15 | let block: (Notification) -> Void 16 | let object: Any? 17 | let queue: OperationQueue? 18 | 19 | init(object: Any?, 20 | queue: OperationQueue?, 21 | block: @escaping (Notification) -> Void) { 22 | self.block = block 23 | self.object = object 24 | self.queue = queue 25 | } 26 | } 27 | 28 | var observers: [String: MockObserver] = [:] 29 | 30 | func addObserver(forName name: NSNotification.Name?, 31 | object: Any?, 32 | queue: OperationQueue?, 33 | using block: @escaping (Notification) -> Void) -> NSObjectProtocol { 34 | guard 35 | let name = name 36 | else { fatalError("Name must be specified for testing") } 37 | 38 | guard 39 | observers[name.rawValue] == nil 40 | else { fatalError("Cannot have multiple observers for same name") } 41 | 42 | observers[name.rawValue] = MockObserver(object: object, 43 | queue: queue, 44 | block: block) 45 | 46 | return name.rawValue as NSString 47 | } 48 | 49 | func post(name: NSNotification.Name, 50 | object: Any?, 51 | userInfo: [AnyHashable: Any]? = nil) { 52 | guard 53 | let observer = observers[name.rawValue] 54 | else { return } 55 | 56 | if let filter = observer.object as AnyObject? { 57 | guard 58 | let object = object as AnyObject?, 59 | filter === object 60 | else { return } 61 | } 62 | 63 | let notification = Notification(name: name, 64 | object: object, 65 | userInfo: userInfo) 66 | 67 | if let queue = observer.queue { 68 | queue.addOperation { observer.block(notification) } 69 | } else { 70 | observer.block(notification) 71 | } 72 | } 73 | 74 | func removeObserver(_ observer: Any) { 75 | guard 76 | let name = observer as? String 77 | else { return } 78 | 79 | observers[name] = nil 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Tests/Mock/MockProcessInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockProcessInfo.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-05-13. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import Foundation 11 | @testable import XestiMonitors 12 | 13 | internal class MockProcessInfo: ProcessInfoProtocol { 14 | init() { 15 | self.isLowPowerModeEnabled = false 16 | self.rawThermalState = 0 17 | } 18 | 19 | var isLowPowerModeEnabled: Bool 20 | 21 | @available(iOS 11.0, OSX 10.10.3, tvOS 11.0, watchOS 4.0, *) 22 | var thermalState: ProcessInfo.ThermalState { 23 | guard 24 | let state = ProcessInfo.ThermalState(rawValue: rawThermalState) 25 | else { return .nominal } 26 | 27 | return state 28 | } 29 | 30 | // MARK: - 31 | 32 | var rawThermalState: Int 33 | } 34 | -------------------------------------------------------------------------------- /Tests/Mock/UIFocusUpdateContext+Neutered.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIFocusUpdateContext+Neutered.h 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-05-05. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | @import UIKit; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface UIFocusUpdateContext (Neutered) 15 | 16 | + (UIFocusUpdateContext *) make; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /Tests/Mock/UIFocusUpdateContext+Neutered.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIFocusUpdateContext+Neutered.m 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-05-05. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | @import ObjectiveC; 11 | 12 | #import "UIFocusUpdateContext+Neutered.h" 13 | 14 | // 15 | // We need to create an instance of `UIFocusUpdateContext` in order to fully 16 | // test `FocusMonitor`. We can easily create such an instance using 17 | // `UIFocusUpdateContext()` on tvOS. For whatever reason, iOS throws an 18 | // exception for that case. So we are forced to rely on this ugly hack in 19 | // Objective-C (apparently one of the few cases where Swift falls short) to 20 | // create the instance. 21 | // 22 | @implementation UIFocusUpdateContext (Neutered) 23 | 24 | + (UIFocusUpdateContext *) make { 25 | return [[UIFocusUpdateContext alloc] initNeutered]; 26 | } 27 | 28 | - (instancetype) initNeutered { 29 | return [super init]; // skip the naughty bits; just initialize super 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Tests/Mock/XestiMonitorsTests-iOS-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "UIFocusUpdateContext+Neutered.h" 2 | -------------------------------------------------------------------------------- /Tests/UIKit/Application/BackgroundRefreshMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackgroundRefreshMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2017-12-27. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class BackgroundRefreshMonitorTests: XCTestCase { 15 | let application = MockApplication() 16 | let notificationCenter = MockNotificationCenter() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | ApplicationInjector.inject = { self.application } 22 | 23 | application.backgroundRefreshStatus = .restricted 24 | 25 | NotificationCenterInjector.inject = { self.notificationCenter } 26 | } 27 | 28 | func testMonitor_statusDidChange() { 29 | let expectation = self.expectation(description: "Handler called") 30 | let expectedStatus: UIBackgroundRefreshStatus = .available 31 | var expectedEvent: BackgroundRefreshMonitor.Event? 32 | let monitor = BackgroundRefreshMonitor(queue: .main) { event in 33 | XCTAssertEqual(OperationQueue.current, .main) 34 | 35 | expectedEvent = event 36 | expectation.fulfill() 37 | } 38 | 39 | monitor.startMonitoring() 40 | simulateStatusDidChange(to: expectedStatus) 41 | waitForExpectations(timeout: 1) 42 | monitor.stopMonitoring() 43 | 44 | if let event = expectedEvent, 45 | case let .statusDidChange(status) = event { 46 | XCTAssertEqual(status, expectedStatus) 47 | } else { 48 | XCTFail("Unexpected event") 49 | } 50 | } 51 | 52 | func testStatus() { 53 | let expectedStatus: UIBackgroundRefreshStatus = .denied 54 | let monitor = BackgroundRefreshMonitor(queue: .main) { _ in 55 | XCTAssertEqual(OperationQueue.current, .main) 56 | } 57 | 58 | simulateStatusDidChange(to: expectedStatus) 59 | 60 | XCTAssertEqual(monitor.status, expectedStatus) 61 | } 62 | 63 | private func simulateStatusDidChange(to status: UIBackgroundRefreshStatus) { 64 | application.backgroundRefreshStatus = status 65 | 66 | notificationCenter.post(name: .UIApplicationBackgroundRefreshStatusDidChange, 67 | object: application) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/UIKit/Application/MemoryMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MemoryMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2017-12-27. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class MemoryMonitorTests: XCTestCase { 15 | let application = MockApplication() 16 | let notificationCenter = MockNotificationCenter() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | ApplicationInjector.inject = { self.application } 22 | 23 | NotificationCenterInjector.inject = { self.notificationCenter } 24 | } 25 | 26 | func testMonitor_didReceiveWarning() { 27 | let expectation = self.expectation(description: "Handler called") 28 | var expectedEvent: MemoryMonitor.Event? 29 | let monitor = MemoryMonitor(queue: .main) { event in 30 | XCTAssertEqual(OperationQueue.current, .main) 31 | 32 | expectedEvent = event 33 | expectation.fulfill() 34 | } 35 | 36 | monitor.startMonitoring() 37 | simulateDidReceiveMemoryWarning() 38 | waitForExpectations(timeout: 1) 39 | monitor.stopMonitoring() 40 | 41 | if let event = expectedEvent { 42 | XCTAssertEqual(event, .didReceiveWarning) 43 | } else { 44 | XCTFail("Unexpected event") 45 | } 46 | } 47 | 48 | private func simulateDidReceiveMemoryWarning() { 49 | notificationCenter.post(name: .UIApplicationDidReceiveMemoryWarning, 50 | object: application) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/UIKit/Application/ScreenshotMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenshotMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2017-12-27. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class ScreenshotMonitorTests: XCTestCase { 15 | let application = MockApplication() 16 | let notificationCenter = MockNotificationCenter() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | ApplicationInjector.inject = { self.application } 22 | 23 | NotificationCenterInjector.inject = { self.notificationCenter } 24 | } 25 | 26 | func testMonitor_userDidTake() { 27 | let expectation = self.expectation(description: "Handler called") 28 | var expectedEvent: ScreenshotMonitor.Event? 29 | let monitor = ScreenshotMonitor(queue: .main) { event in 30 | XCTAssertEqual(OperationQueue.current, .main) 31 | 32 | expectedEvent = event 33 | expectation.fulfill() 34 | } 35 | 36 | monitor.startMonitoring() 37 | simulateUserDidTake() 38 | waitForExpectations(timeout: 1) 39 | monitor.stopMonitoring() 40 | 41 | if let event = expectedEvent { 42 | XCTAssertEqual(event, .userDidTake) 43 | } else { 44 | XCTFail("Unexpected event") 45 | } 46 | } 47 | 48 | private func simulateUserDidTake() { 49 | notificationCenter.post(name: .UIApplicationUserDidTakeScreenshot, 50 | object: application) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/UIKit/Application/TimeMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2017-12-27. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class TimeMonitorTests: XCTestCase { 15 | let application = MockApplication() 16 | let notificationCenter = MockNotificationCenter() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | ApplicationInjector.inject = { self.application } 22 | 23 | NotificationCenterInjector.inject = { self.notificationCenter } 24 | } 25 | 26 | func testMonitor_significantChange() { 27 | let expectation = self.expectation(description: "Handler called") 28 | var expectedEvent: TimeMonitor.Event? 29 | let monitor = TimeMonitor(queue: .main) { event in 30 | XCTAssertEqual(OperationQueue.current, .main) 31 | 32 | expectedEvent = event 33 | expectation.fulfill() 34 | } 35 | 36 | monitor.startMonitoring() 37 | simulateSignificantChange() 38 | waitForExpectations(timeout: 1) 39 | monitor.stopMonitoring() 40 | 41 | if let event = expectedEvent { 42 | XCTAssertEqual(event, .significantChange) 43 | } else { 44 | XCTFail("Unexpected event") 45 | } 46 | } 47 | 48 | private func simulateSignificantChange() { 49 | notificationCenter.post(name: .UIApplicationSignificantTimeChange, 50 | object: application) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/UIKit/Device/OrientationMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OrientationMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2017-12-27. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class OrientationMonitorTests: XCTestCase { 15 | let device = MockDevice() 16 | let notificationCenter = MockNotificationCenter() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | DeviceInjector.inject = { self.device } 22 | 23 | device.orientation = .unknown 24 | 25 | NotificationCenterInjector.inject = { self.notificationCenter } 26 | } 27 | 28 | func testMonitor_didChange() { 29 | let expectation = self.expectation(description: "Handler called") 30 | let expectedOrientation: UIDeviceOrientation = .portrait 31 | var expectedEvent: OrientationMonitor.Event? 32 | let monitor = OrientationMonitor(queue: .main) { event in 33 | XCTAssertEqual(OperationQueue.current, .main) 34 | 35 | expectedEvent = event 36 | expectation.fulfill() 37 | } 38 | 39 | monitor.startMonitoring() 40 | simulateDidChange(to: expectedOrientation) 41 | waitForExpectations(timeout: 1) 42 | monitor.stopMonitoring() 43 | 44 | if let event = expectedEvent, 45 | case let .didChange(orientation) = event { 46 | XCTAssertEqual(orientation, expectedOrientation) 47 | } else { 48 | XCTFail("Unexpected event") 49 | } 50 | } 51 | 52 | func testOrientation() { 53 | let expectedOrientation: UIDeviceOrientation = .landscapeRight 54 | let monitor = OrientationMonitor(queue: .main) { _ in 55 | XCTAssertEqual(OperationQueue.current, .main) 56 | } 57 | 58 | simulateDidChange(to: expectedOrientation) 59 | 60 | XCTAssertEqual(monitor.orientation, expectedOrientation) 61 | } 62 | 63 | private func simulateDidChange(to orientation: UIDeviceOrientation) { 64 | device.orientation = orientation 65 | 66 | notificationCenter.post(name: .UIDeviceOrientationDidChange, 67 | object: device) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/UIKit/Device/ProximityMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProximityMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2017-12-27. 6 | // 7 | // © 2017 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class ProximityMonitorTests: XCTestCase { 15 | let device = MockDevice() 16 | let notificationCenter = MockNotificationCenter() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | DeviceInjector.inject = { self.device } 22 | 23 | device.proximityState = false 24 | 25 | NotificationCenterInjector.inject = { self.notificationCenter } 26 | } 27 | 28 | func testIsAvailable() { 29 | let monitor = ProximityMonitor(queue: .main) { _ in 30 | XCTAssertEqual(OperationQueue.current, .main) 31 | } 32 | 33 | device.isProximityMonitoringEnabled = false 34 | 35 | XCTAssertFalse(device.isProximityMonitoringEnabled) 36 | XCTAssertTrue(monitor.isAvailable) 37 | XCTAssertFalse(device.isProximityMonitoringEnabled) 38 | } 39 | 40 | func testMonitor_stateDidChange() { 41 | let expectation = self.expectation(description: "Handler called") 42 | let expectedState: Bool = true 43 | var expectedEvent: ProximityMonitor.Event? 44 | let monitor = ProximityMonitor(queue: .main) { event in 45 | XCTAssertEqual(OperationQueue.current, .main) 46 | 47 | expectedEvent = event 48 | expectation.fulfill() 49 | } 50 | 51 | monitor.startMonitoring() 52 | simulateStateDidChange(to: expectedState) 53 | waitForExpectations(timeout: 1) 54 | monitor.stopMonitoring() 55 | 56 | if let event = expectedEvent, 57 | case let .stateDidChange(state) = event { 58 | XCTAssertEqual(state, expectedState) 59 | } else { 60 | XCTFail("Unexpected event") 61 | } 62 | } 63 | 64 | func testState() { 65 | let expectedState: Bool = true 66 | let monitor = ProximityMonitor(queue: .main) { _ in 67 | XCTAssertEqual(OperationQueue.current, .main) 68 | } 69 | 70 | simulateStateDidChange(to: expectedState) 71 | 72 | XCTAssertEqual(monitor.state, expectedState) 73 | } 74 | 75 | private func simulateStateDidChange(to state: Bool) { 76 | device.proximityState = state 77 | 78 | notificationCenter.post(name: .UIDeviceProximityStateDidChange, 79 | object: device) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Tests/UIKit/Other/DocumentStateMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DocumentStateMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-02-16. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class DocumentStateMonitorTests: XCTestCase { 15 | let document = UIDocument(fileURL: Bundle.main.bundleURL) 16 | let notificationCenter = MockNotificationCenter() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | NotificationCenterInjector.inject = { self.notificationCenter } 22 | } 23 | 24 | func testMonitor_stateDidChange() { 25 | let expectation = self.expectation(description: "Handler called") 26 | var expectedEvent: DocumentStateMonitor.Event? 27 | let monitor = DocumentStateMonitor(document: document, 28 | queue: .main) { event in 29 | XCTAssertEqual(OperationQueue.current, .main) 30 | 31 | expectedEvent = event 32 | expectation.fulfill() 33 | } 34 | 35 | monitor.startMonitoring() 36 | simulateDidChange() 37 | waitForExpectations(timeout: 1) 38 | monitor.stopMonitoring() 39 | 40 | if let event = expectedEvent, 41 | case let .didChange(test) = event { 42 | XCTAssertEqual(test, document) 43 | } else { 44 | XCTFail("Unexpected event") 45 | } 46 | } 47 | 48 | private func simulateDidChange() { 49 | notificationCenter.post(name: .UIDocumentStateChanged, 50 | object: document) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/UIKit/Other/TableViewSelectionMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewSelectionMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by Rose Maina on 2018-04-20. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class TableViewSelectionMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | let tableView = UITableView() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | NotificationCenterInjector.inject = { self.notificationCenter } 22 | } 23 | 24 | func testMonitor_didChange() { 25 | let expectation = self.expectation(description: "Hander called") 26 | var expectedEvent: TableViewSelectionMonitor.Event? 27 | let monitor = TableViewSelectionMonitor(tableView: self.tableView, 28 | queue: .main) { event in 29 | XCTAssertEqual(OperationQueue.current, .main) 30 | 31 | expectedEvent = event 32 | expectation.fulfill() 33 | } 34 | 35 | monitor.startMonitoring() 36 | simulateDidChange() 37 | waitForExpectations(timeout: 1) 38 | monitor.stopMonitoring() 39 | 40 | if let event = expectedEvent, 41 | case let .didChange(test) = event { 42 | XCTAssertEqual(test, tableView) 43 | } else { 44 | XCTFail("Unexpected Event") 45 | } 46 | } 47 | 48 | private func simulateDidChange() { 49 | notificationCenter.post(name: .UITableViewSelectionDidChange, 50 | object: tableView) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/UIKit/Other/ViewControllerShowDetailTargetMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerShowDetailTargetMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-02-16. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class ViewControllerShowDetailTargetMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | let viewController = UIViewController() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | NotificationCenterInjector.inject = { self.notificationCenter } 22 | } 23 | 24 | func testMonitor_stateDidChange() { 25 | let expectation = self.expectation(description: "Handler called") 26 | var expectedEvent: ViewControllerShowDetailTargetMonitor.Event? 27 | let monitor = ViewControllerShowDetailTargetMonitor(queue: .main) { event in 28 | XCTAssertEqual(OperationQueue.current, .main) 29 | 30 | expectedEvent = event 31 | expectation.fulfill() 32 | } 33 | 34 | monitor.startMonitoring() 35 | simulateDidChange() 36 | waitForExpectations(timeout: 1) 37 | monitor.stopMonitoring() 38 | 39 | if let event = expectedEvent, 40 | case let .didChange(test) = event { 41 | XCTAssertEqual(test, viewController) 42 | } else { 43 | XCTFail("Unexpected event") 44 | } 45 | } 46 | 47 | private func simulateDidChange() { 48 | notificationCenter.post(name: .UIViewControllerShowDetailTargetDidChange, 49 | object: viewController) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/UIKit/Screen/ScreenBrightnessMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenBrightnessMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by Paul Nyondo on 2018-03-25. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class ScreenBrightnessMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | let screen = UIScreen() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | NotificationCenterInjector.inject = { self.notificationCenter } 22 | } 23 | 24 | func testMonitor_didChange() { 25 | let expectation = self.expectation(description: "Handler called") 26 | var expectedEvent: ScreenBrightnessMonitor.Event? 27 | let monitor = ScreenBrightnessMonitor(screen: screen, 28 | queue: .main) { event in 29 | XCTAssertEqual(OperationQueue.current, .main) 30 | 31 | expectedEvent = event 32 | expectation.fulfill() 33 | } 34 | 35 | monitor.startMonitoring() 36 | simulateDidChange() 37 | waitForExpectations(timeout: 1) 38 | monitor.stopMonitoring() 39 | 40 | if let event = expectedEvent, 41 | case let .didChange(test) = event { 42 | XCTAssertEqual(test, screen) 43 | } else { 44 | XCTFail("Unexpected event") 45 | } 46 | } 47 | 48 | private func simulateDidChange() { 49 | notificationCenter.post(name: .UIScreenBrightnessDidChange, 50 | object: screen) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/UIKit/Screen/ScreenCapturedMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenCapturedMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by Paul Nyondo on 2018-04-06. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md). 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class ScreenCapturedMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | let screen = UIScreen() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | NotificationCenterInjector.inject = { self.notificationCenter } 22 | } 23 | 24 | func testMonitor_didChange() { 25 | if #available(iOS 11.0, tvOS 11.0, *) { 26 | let expectation = self.expectation(description: "Handler called") 27 | var expectedEvent: ScreenCapturedMonitor.Event? 28 | let monitor = ScreenCapturedMonitor(screen: screen, 29 | queue: .main) { event in 30 | XCTAssertEqual(OperationQueue.current, .main) 31 | 32 | expectedEvent = event 33 | expectation.fulfill() 34 | } 35 | 36 | monitor.startMonitoring() 37 | simulateDidChange() 38 | waitForExpectations(timeout: 1) 39 | monitor.stopMonitoring() 40 | 41 | if let event = expectedEvent, 42 | case let .didChange(test) = event { 43 | XCTAssertEqual(test, screen) 44 | } else { 45 | XCTFail("Unexpected event") 46 | } 47 | } 48 | } 49 | 50 | private func simulateDidChange() { 51 | if #available(iOS 11.0, tvOS 11.0, *) { 52 | notificationCenter.post(name: .UIScreenCapturedDidChange, 53 | object: screen) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/UIKit/Screen/ScreenConnectionMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenConnectionMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by Paul Nyondo on 2018-04-04. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class ScreenConnectionMonitorTest: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | let screen = UIScreen() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | NotificationCenterInjector.inject = { self.notificationCenter } 21 | } 22 | 23 | func testMonitor_didConnect() { 24 | let expectation = self.expectation(description: "Handler called") 25 | var expectedEvent: ScreenConnectionMonitor.Event? 26 | let monitor = ScreenConnectionMonitor(options: .didConnect, 27 | queue: .main) { event in 28 | XCTAssertEqual(OperationQueue.current, .main) 29 | 30 | expectedEvent = event 31 | expectation.fulfill() 32 | } 33 | 34 | monitor.startMonitoring() 35 | simulateDidConnect() 36 | waitForExpectations(timeout: 1) 37 | monitor.stopMonitoring() 38 | 39 | if let event = expectedEvent, 40 | case let .didConnect(test) = event { 41 | XCTAssertEqual(test, screen) 42 | } else { 43 | XCTFail("Unexpected event") 44 | } 45 | } 46 | 47 | func testMonitor_didDisconnect() { 48 | let expectation = self.expectation(description: "Handler called") 49 | var expectedEvent: ScreenConnectionMonitor.Event? 50 | let monitor = ScreenConnectionMonitor(options: .didDisconnect, 51 | queue: .main) { event in 52 | XCTAssertEqual(OperationQueue.current, .main) 53 | 54 | expectedEvent = event 55 | expectation.fulfill() 56 | } 57 | 58 | monitor.startMonitoring() 59 | simulateDidDisconnect() 60 | waitForExpectations(timeout: 1) 61 | monitor.stopMonitoring() 62 | 63 | if let event = expectedEvent, 64 | case let .didDisconnect(test) = event { 65 | XCTAssertEqual(test, screen) 66 | } else { 67 | XCTFail("Unexpected event") 68 | } 69 | } 70 | 71 | private func simulateDidConnect() { 72 | notificationCenter.post(name: .UIScreenDidConnect, 73 | object: screen) 74 | } 75 | 76 | private func simulateDidDisconnect() { 77 | notificationCenter.post(name: .UIScreenDidDisconnect, 78 | object: screen) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Tests/UIKit/Screen/ScreenModeMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenModeMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by Paul Nyondo on 2018-03-31. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md). 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class ScreenModeMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | let screen = UIScreen() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | NotificationCenterInjector.inject = { self.notificationCenter } 22 | } 23 | 24 | func testMonitor_didChange() { 25 | let expectation = self.expectation(description: "Handler called") 26 | var expectedEvent: ScreenModeMonitor.Event? 27 | let monitor = ScreenModeMonitor(screen: screen, 28 | queue: .main) { event in 29 | XCTAssertEqual(OperationQueue.current, .main) 30 | 31 | expectedEvent = event 32 | expectation.fulfill() 33 | } 34 | 35 | monitor.startMonitoring() 36 | simulateDidChange() 37 | waitForExpectations(timeout: 1) 38 | monitor.stopMonitoring() 39 | 40 | if let event = expectedEvent, 41 | case let .didChange(test) = event { 42 | XCTAssertEqual(test, screen) 43 | } else { 44 | XCTFail("Unexpected event") 45 | } 46 | } 47 | 48 | private func simulateDidChange() { 49 | notificationCenter.post(name: .UIScreenModeDidChange, 50 | object: screen) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/UIKit/Text/TextInputModeMonitorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextInputModeMonitorTests.swift 3 | // XestiMonitorsTests 4 | // 5 | // Created by J. G. Pusey on 2018-04-07. 6 | // 7 | // © 2018 J. G. Pusey (see LICENSE.md) 8 | // 9 | 10 | import UIKit 11 | import XCTest 12 | @testable import XestiMonitors 13 | 14 | internal class TextInputModeMonitorTests: XCTestCase { 15 | let notificationCenter = MockNotificationCenter() 16 | let textInputMode = UITextInputMode() 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | NotificationCenterInjector.inject = { self.notificationCenter } 22 | } 23 | 24 | func testMonitor_didChange() { 25 | let expectation = self.expectation(description: "Handler called") 26 | var expectedEvent: TextInputModeMonitor.Event? 27 | let monitor = TextInputModeMonitor(queue: .main) { event in 28 | XCTAssertEqual(OperationQueue.current, .main) 29 | 30 | expectedEvent = event 31 | expectation.fulfill() 32 | } 33 | 34 | monitor.startMonitoring() 35 | simulateDidChange() 36 | waitForExpectations(timeout: 1) 37 | monitor.stopMonitoring() 38 | 39 | if let event = expectedEvent, 40 | case let .didChange(test) = event { 41 | XCTAssertEqual(test, textInputMode) 42 | } else { 43 | XCTFail("Unexpected Event") 44 | } 45 | } 46 | 47 | private func simulateDidChange() { 48 | notificationCenter.post(name: .UITextInputCurrentInputModeDidChange, 49 | object: textInputMode) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /XestiMonitors.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'XestiMonitors' 3 | s.version = '2.12.1' 4 | s.swift_version = '4.0' 5 | s.authors = { 'J. G. Pusey' => 'ebardx@gmail.com' } 6 | s.license = { :type => 'MIT', 7 | :file => 'LICENSE.md' } 8 | s.homepage = 'https://github.com/eBardX/XestiMonitors' 9 | s.source = { :git => 'https://github.com/eBardX/XestiMonitors.git', 10 | :tag => "v#{s.version}" } 11 | s.summary = 'An extensible monitoring framework written in Swift.' 12 | s.documentation_url = 'https://ebardx.github.io/XestiMonitors/' 13 | 14 | s.ios.deployment_target = '9.0' 15 | s.osx.deployment_target = '10.10' 16 | s.tvos.deployment_target = '9.0' 17 | s.watchos.deployment_target = '2.0' 18 | 19 | s.requires_arc = true 20 | 21 | s.ios.frameworks = 'CoreLocation', 'CoreMotion', 'Foundation', 'SystemConfiguration', 'UIKit' 22 | s.osx.frameworks = 'CoreLocation', 'Foundation', 'SystemConfiguration' 23 | s.tvos.frameworks = 'CoreLocation', 'Foundation', 'SystemConfiguration', 'UIKit' 24 | s.watchos.frameworks = 'CoreLocation', 'CoreMotion', 'Foundation' 25 | 26 | s.default_subspec = 'Core' 27 | 28 | s.subspec 'Core' do |ss| 29 | ss.source_files = 'Sources/Core/**/*.swift' 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /XestiMonitors.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XestiMonitors.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 100% 23 | 24 | 25 | 100% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/docsets/XestiMonitors.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.xestimonitors 7 | CFBundleName 8 | XestiMonitors 9 | DocSetPlatformFamily 10 | xestimonitors 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/docsets/XestiMonitors.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/docs/docsets/XestiMonitors.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/XestiMonitors.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/docs/docsets/XestiMonitors.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/XestiMonitors.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/docs/docsets/XestiMonitors.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/XestiMonitors.docset/Contents/Resources/Documents/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/docs/docsets/XestiMonitors.docset/Contents/Resources/Documents/img/spinner.gif -------------------------------------------------------------------------------- /docs/docsets/XestiMonitors.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | $content = link.parent().parent().next(); 27 | $content.slideToggle(animationDuration); 28 | 29 | // Keeps the document from jumping to the hash. 30 | var href = $(this).attr('href'); 31 | if (history.pushState) { 32 | history.pushState({}, '', href); 33 | } else { 34 | location.hash = href; 35 | } 36 | event.preventDefault(); 37 | }); 38 | 39 | // Dumb down quotes within code blocks that delimit strings instead of quotations 40 | // https://github.com/realm/jazzy/issues/714 41 | $("code q").replaceWith(function () { 42 | return ["\"", $(this).contents(), "\""]; 43 | }); 44 | -------------------------------------------------------------------------------- /docs/docsets/XestiMonitors.docset/Contents/Resources/Documents/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var searchIndex = lunr(function() { 3 | this.ref('url'); 4 | this.field('name'); 5 | }); 6 | 7 | var $typeahead = $('[data-typeahead]'); 8 | var $form = $typeahead.parents('form'); 9 | var searchURL = $form.attr('action'); 10 | 11 | function displayTemplate(result) { 12 | return result.name; 13 | } 14 | 15 | function suggestionTemplate(result) { 16 | var t = '
'; 17 | t += '' + result.name + ''; 18 | if (result.parent_name) { 19 | t += '' + result.parent_name + ''; 20 | } 21 | t += '
'; 22 | return t; 23 | } 24 | 25 | $typeahead.one('focus', function() { 26 | $form.addClass('loading'); 27 | 28 | $.getJSON(searchURL).then(function(searchData) { 29 | $.each(searchData, function (url, doc) { 30 | searchIndex.add({url: url, name: doc.name}); 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3 37 | }, 38 | { 39 | limit: 10, 40 | display: displayTemplate, 41 | templates: { suggestion: suggestionTemplate }, 42 | source: function(query, sync) { 43 | var results = searchIndex.search(query).map(function(result) { 44 | var doc = searchData[result.ref]; 45 | doc.url = result.ref; 46 | return doc; 47 | }); 48 | sync(results); 49 | } 50 | } 51 | ); 52 | $form.removeClass('loading'); 53 | $typeahead.trigger('focus'); 54 | }); 55 | }); 56 | 57 | var baseURL = searchURL.slice(0, -"search.json".length); 58 | 59 | $typeahead.on('typeahead:select', function(e, result) { 60 | window.location = baseURL + result.url; 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /docs/docsets/XestiMonitors.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/docs/docsets/XestiMonitors.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/XestiMonitors.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/docs/docsets/XestiMonitors.tgz -------------------------------------------------------------------------------- /docs/docsets/XestiMonitors.xml: -------------------------------------------------------------------------------- 1 | 2.12.1https://eBardX.github.io/XestiMonitors/reference/docsets/XestiMonitors.tgz 2 | -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/docs/img/gh.png -------------------------------------------------------------------------------- /docs/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBardX/XestiMonitors/d5dc9aa24b9e84530f3f026f328cec8904215179/docs/img/spinner.gif -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | $content = link.parent().parent().next(); 27 | $content.slideToggle(animationDuration); 28 | 29 | // Keeps the document from jumping to the hash. 30 | var href = $(this).attr('href'); 31 | if (history.pushState) { 32 | history.pushState({}, '', href); 33 | } else { 34 | location.hash = href; 35 | } 36 | event.preventDefault(); 37 | }); 38 | 39 | // Dumb down quotes within code blocks that delimit strings instead of quotations 40 | // https://github.com/realm/jazzy/issues/714 41 | $("code q").replaceWith(function () { 42 | return ["\"", $(this).contents(), "\""]; 43 | }); 44 | -------------------------------------------------------------------------------- /docs/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var searchIndex = lunr(function() { 3 | this.ref('url'); 4 | this.field('name'); 5 | }); 6 | 7 | var $typeahead = $('[data-typeahead]'); 8 | var $form = $typeahead.parents('form'); 9 | var searchURL = $form.attr('action'); 10 | 11 | function displayTemplate(result) { 12 | return result.name; 13 | } 14 | 15 | function suggestionTemplate(result) { 16 | var t = '
'; 17 | t += '' + result.name + ''; 18 | if (result.parent_name) { 19 | t += '' + result.parent_name + ''; 20 | } 21 | t += '
'; 22 | return t; 23 | } 24 | 25 | $typeahead.one('focus', function() { 26 | $form.addClass('loading'); 27 | 28 | $.getJSON(searchURL).then(function(searchData) { 29 | $.each(searchData, function (url, doc) { 30 | searchIndex.add({url: url, name: doc.name}); 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3 37 | }, 38 | { 39 | limit: 10, 40 | display: displayTemplate, 41 | templates: { suggestion: suggestionTemplate }, 42 | source: function(query, sync) { 43 | var results = searchIndex.search(query).map(function(result) { 44 | var doc = searchData[result.ref]; 45 | doc.url = result.ref; 46 | return doc; 47 | }); 48 | sync(results); 49 | } 50 | } 51 | ); 52 | $form.removeClass('loading'); 53 | $typeahead.trigger('focus'); 54 | }); 55 | }); 56 | 57 | var baseURL = searchURL.slice(0, -"search.json".length); 58 | 59 | $typeahead.on('typeahead:select', function(e, result) { 60 | window.location = baseURL + result.url; 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /docs/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | 4 | ], 5 | "source_directory": "/Users/jgp/Programming/Xesticode/Public/XestiMonitors" 6 | } --------------------------------------------------------------------------------