?) -> 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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-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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------