├── Cartfile
├── Cartfile.resolved
├── CarthageTests
├── Cartfile
└── Eunomia.xcodeproj
│ ├── project.xcworkspace
│ └── contents.xcworkspacedata
│ ├── xcshareddata
│ └── xcschemes
│ │ └── EunomiaCocoapodiOSTests.xcscheme
│ └── project.pbxproj
├── Gemfile
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Eunomia.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── Eunomia-iOS.xcscheme
│ └── Eunomia-macOS.xcscheme
├── CHANGELOG.md
├── CocoapodTests
├── Eunomia.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── EunomiaCocoapodiOSTests.xcscheme
│ └── project.pbxproj
├── Podfile
├── Eunomia.xcworkspace
│ └── contents.xcworkspacedata
└── Podfile.lock
├── Source
├── Core
│ ├── UtilityExtensions
│ │ ├── Views
│ │ │ ├── UIStackView+Utility.swift
│ │ │ ├── UITextView+Utility.swift
│ │ │ ├── UIBarItem+Utility.swift
│ │ │ ├── UITableViewCell+Utility.swift
│ │ │ ├── UIViewController+Utility.swift
│ │ │ ├── UIWebView+Utility.swift
│ │ │ ├── UITextField+Utility.swift
│ │ │ ├── UITabBar+Utility.swift
│ │ │ ├── UIProgressView+Utility.swift
│ │ │ ├── UICollectionViewFlowLayout+Utility.swift
│ │ │ ├── UICollectionView+Utility.swift
│ │ │ ├── UIScrollView+Utility.swift
│ │ │ ├── UITableView+Utility.swift
│ │ │ ├── UIAlertController+Utility.swift
│ │ │ ├── UITabBarController+Utility.swift
│ │ │ └── UIImageView+Utility.swift
│ │ ├── GCD+Utility.swift
│ │ ├── AV
│ │ │ ├── AVFoundation+Utility.swift
│ │ │ └── AVURLAsset+Utility.swift
│ │ ├── NSRange+Utility.swift
│ │ ├── NSNotification+Utility.swift
│ │ ├── CLLocationSpeed+Utility.swift
│ │ ├── CGRect.swift
│ │ ├── KeyedEncodingContainer+Utility.swift
│ │ ├── ClosedRange+Utility.swift
│ │ ├── CLLocationDegrees+Utility.swift
│ │ ├── Dictionary+Utility.swift
│ │ ├── UIDevice+Utility.swift
│ │ ├── CoreData
│ │ │ ├── NSManagedObjectContext+Utility.swift
│ │ │ ├── NSFetchedResultsController+Utility.swift
│ │ │ └── NSManagedObject+Utility.swift
│ │ ├── CNContactStore+Utility.swift
│ │ ├── Date+Utility.swift
│ │ ├── JSContext+Utility.swift
│ │ ├── CLLocationDirection+Utility.swift
│ │ ├── KeyedDecodingContainer+Utility.swift
│ │ ├── NSCharacterSet+Utility.swift
│ │ ├── RangeReplaceableCollectionType+Utility.swift
│ │ ├── NSDataAsset.swift
│ │ ├── MPMediaItem+Utility.swift
│ │ ├── NSAttributedString+Utility.swift
│ │ ├── DispatchQueue+Utility.swift
│ │ ├── Array+Utility.swift
│ │ ├── NSError+Utility.swift
│ │ ├── UIApplication+Utility.swift
│ │ ├── CLLocationCoordinate2D+Utility.swift
│ │ ├── UIFont+Utility.swift
│ │ ├── Measurement+Utility.swift
│ │ ├── String+Utility.swift
│ │ ├── PHPhotoLibrary+Utility.swift
│ │ ├── CLLocation+Utility.swift
│ │ ├── NSColor+Utility.swift
│ │ ├── UIColor+Utility.swift
│ │ └── NSObject+Utility.swift
│ ├── Misc
│ │ ├── SuccessResult.swift
│ │ ├── Guarantee.swift
│ │ ├── Errors.swift
│ │ ├── System.swift
│ │ ├── Tangent.swift
│ │ ├── OrdinalNumberFormatter.swift
│ │ └── ManagedObjectsObserver.swift
│ └── App
│ │ ├── CocoaLumberjackDeprecation.swift
│ │ ├── AppExtensionSafeApplication.swift
│ │ ├── EunomiaAppDelegate.swift
│ │ ├── Logging.swift
│ │ └── Location.swift
└── Framework
│ ├── Eunomia.h
│ └── Info.plist
├── .gitignore
├── Package.swift
├── Eunomia.podspec
├── Tests
├── Info.plist
├── NSCharacterSet+UtilityTests.swift
├── SystemTests.swift
├── MeasurementTest.swift
└── String+UtilityTests.swift
├── LICENSE.md
└── Gemfile.lock
/Cartfile:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/CarthageTests/Cartfile:
--------------------------------------------------------------------------------
1 | github "Adorkable/Eunomia"
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'cocoapods'
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Eunomia.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [3.0.0](https://github.com/Adorkable/Eunomia/tree/3.0.0) (2015-12-23)
4 |
5 |
6 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
--------------------------------------------------------------------------------
/CarthageTests/Eunomia.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CocoapodTests/Eunomia.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CocoapodTests/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '6.0'
3 |
4 | use_frameworks!
5 |
6 | target 'EunomiaCocoapodiOSTests' do
7 | pod 'CocoaLumberjack', '~> 2.2.0'
8 | pod 'Eunomia', :path => '../'
9 | end
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UIStackView+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIStackView+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 6/20/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UITextView+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITextView+Utility.swift
3 | // Pods
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | //
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UITextView {
13 |
14 | }
15 | #endif
16 |
--------------------------------------------------------------------------------
/CocoapodTests/Eunomia.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Eunomia.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Source/Core/Misc/SuccessResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SuccessResult.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/12/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public enum SuccessResult {
12 | case success(T)
13 | case failure(NSError)
14 | }
15 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/GCD+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GCD+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | func dispatchOnMain(_ block : @escaping ()->()) {
12 | DispatchQueue.main.async(execute: block)
13 | }
14 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UIBarItem+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIBarItem+Utility.swift
3 | // Pods
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | //
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UIBarItem {
13 |
14 | public var view : UIView? {
15 | return self.value(forKey: "view") as? UIView
16 | }
17 | }
18 | #endif
19 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/AV/AVFoundation+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AVFoundation+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 6/22/17.
6 | // Copyright © 2017 Adorkable. All rights reserved.
7 | //
8 |
9 | import AVFoundation
10 |
11 | extension AVPlayer {
12 | public var isPlaying: Bool {
13 | return rate != 0 && error == nil
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/NSRange+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSRange+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/25/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NSRange {
12 |
13 | func isValid() -> Bool {
14 | return self.location != NSNotFound && self.length > 0
15 | }
16 | }
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/NSNotification+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSNotification+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 7/29/15.
6 | // Copyright (c) 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Notification {
12 |
13 | public func objectFromInfo(_ key : String) -> AnyObject? {
14 | return self.userInfo?[key] as AnyObject
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Source/Core/App/CocoaLumberjackDeprecation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CocoaLumberjackDeprecation.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/12/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | @available(*, deprecated)
12 | typealias DDLog = Log
13 | @available(*, deprecated)
14 | typealias DDLogLevel = Log.Level
15 | @available(*, deprecated)
16 | typealias DDLogFlag = Log.Level
17 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/CLLocationSpeed+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLLocationSpeed+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 9/22/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import CoreLocation
10 |
11 | extension CLLocationSpeed {
12 | public var unitSpeed: Measurement {
13 | return Measurement(value: self, unit: UnitSpeed.metersPerSecond)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Source/Core/Misc/Guarantee.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Guarantee.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/12/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public func guarantee(_ potential : T?, fallback : @autoclosure () -> T) -> T {
12 |
13 | let result : T
14 | if let actual = potential {
15 | result = actual
16 | } else {
17 | result = fallback()
18 | }
19 | return result
20 | }
21 |
--------------------------------------------------------------------------------
/Source/Core/Misc/Errors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Errors.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 6/7/17.
6 | // Copyright © 2017 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class SharedInstanceIsNilError: Error {
12 | let ofType: AnyClass
13 | init(ofType: AnyClass) {
14 | self.ofType = ofType
15 | }
16 | }
17 |
18 | class UnableToCreateNumberFromStringError: Error {
19 | let string: String
20 | init(string: String) {
21 | self.string = string
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/CGRect.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGRect.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 1/25/19.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 | #elseif os(macOS)
12 | import Cocoa
13 | #endif
14 |
15 | extension CGRect {
16 | public init(center: CGPoint, size: CGSize) {
17 | let origin = CGPoint(x: center.x - size.width / 2,
18 | y: center.y - size.height / 2)
19 | self.init(origin: origin, size: size)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/KeyedEncodingContainer+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyedEncodingContainer.swift
3 | // GTFSKit
4 | //
5 | // Created by Ian Grossberg on 9/16/18.
6 | // Copyright © 2018 Jack Wilsdon. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension KeyedEncodingContainer {
12 | public mutating func encode(_ value: Date, forKey key: KeyedEncodingContainer.Key, formatter: DateFormatter) throws {
13 | let stringValue = formatter.string(from: value)
14 |
15 | try self.encode(stringValue, forKey: key)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/ClosedRange+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClosedRange+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 8/24/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // TODO: how do I make it more generic than UnitAngle
12 | extension ClosedRange where Bound == Measurement {
13 | public var value: ClosedRange {
14 | let uncheckedBounds = (self.lowerBound.value, self.upperBound.value)
15 | return ClosedRange(uncheckedBounds: uncheckedBounds)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/CLLocationDegrees+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLLocationDegrees+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 8/24/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreLocation
11 |
12 | extension CLLocationDegrees {
13 | public static let latitudeMinimum: CLLocationDegrees = -90
14 | public static let latitudeMaximum: CLLocationDegrees = 90
15 | public static let longitudeMinimum: CLLocationDegrees = -180
16 | public static let longitudeMaximum: CLLocationDegrees = 180
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | .ruby-version
4 |
5 | .gitmodules
6 |
7 | # Xcode
8 | #
9 | build/
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata
19 | *.xccheckout
20 | *.moved-aside
21 | DerivedData
22 | *.hmap
23 | *.ipa
24 | *.xcuserstate
25 |
26 | # Cocoapods
27 | #
28 | Pods/
29 | */Pods/
30 |
31 | # Carthage
32 | #
33 | Carthage/Checkouts
34 | Carthage/Build
35 | */Carthage/Checkouts
36 | */Carthage/Build
37 |
38 |
39 |
40 | # Accio dependency management
41 | Dependencies/
42 | .accio/
43 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Dictionary+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dictionary+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Dictionary {
12 |
13 | public mutating func updateValue(_ value: Value?, forKey key: Key) -> Value? {
14 |
15 | if let value = value {
16 |
17 | self[key] = value
18 | } else {
19 |
20 | self.removeValue(forKey: key)
21 | }
22 |
23 | return value
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/UIDevice+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIDevice+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 7/24/15.
6 | // Copyright (c) 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UIDevice
13 | {
14 | public var isSimulator : Bool {
15 | #if targetEnvironment(simulator)
16 | return true
17 | #else
18 | return false
19 | #endif
20 | }
21 |
22 | public var vendorUUIDString : String? {
23 | return self.identifierForVendor?.uuidString
24 | }
25 | }
26 | #endif
27 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "Eunomia",
6 | platforms: [
7 | .macOS(.v10_12), .iOS(.v10),
8 | ],
9 | products: [
10 | .library(name: "Eunomia", targets: ["Eunomia"]),
11 | ],
12 | targets: [
13 | .target(
14 | name: "Eunomia",
15 | path: "Source"
16 | ),
17 | // Ok: Swift Package Manager doesn't allow overlapping source???
18 | // .target(
19 | // name: "Eunomia-macOS",
20 | // dependencies: [
21 | // ],
22 | // path: "Source"
23 | // ),
24 | ]
25 | )
26 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UITableViewCell+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableViewCell+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UITableViewCell {
13 |
14 | public func contentViewContraint(_ attribute : NSLayoutConstraint.Attribute) -> [NSLayoutConstraint]
15 | {
16 | return self.contentView.constraints.filter { (constraint) -> Bool in
17 | return constraint.firstAttribute == attribute && constraint.firstItem as? NSObject == self.contentView
18 | }
19 | }
20 | }
21 | #endif
22 |
--------------------------------------------------------------------------------
/Source/Framework/Eunomia.h:
--------------------------------------------------------------------------------
1 | //
2 | // Eunomia.h
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 7/24/15.
6 | // Copyright (c) 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #if TARGET_OS_IPHONE
12 | #import
13 | #elif TARGET_OS_MAC
14 | #import
15 | #endif
16 |
17 | //! Project version number for Eunomia.
18 | FOUNDATION_EXPORT double EunomiaVersionNumber;
19 |
20 | //! Project version string for Eunomia.
21 | FOUNDATION_EXPORT const unsigned char EunomiaVersionString[];
22 |
23 | // In this header, you should import all the public headers of your framework using statements like #import
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/AV/AVURLAsset+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AVURLAsset+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 6/20/17.
6 | // Copyright © 2017 Adorkable. All rights reserved.
7 | //
8 |
9 | import AVFoundation
10 | #if os(iOS)
11 | import UIKit
12 | #endif
13 |
14 | extension AVURLAsset {
15 | #if os(iOS)
16 | public func firstFrame() throws -> UIImage {
17 | let generate = AVAssetImageGenerator(asset: self)
18 | generate.appliesPreferredTrackTransform = true
19 | let time = CMTimeMake(value: 1, timescale: 60)
20 |
21 | let imageReference = try generate.copyCGImage(at: time, actualTime: nil)
22 | return UIImage(cgImage: imageReference)
23 | }
24 | #endif
25 | }
26 |
--------------------------------------------------------------------------------
/Eunomia.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 |
3 | s.name = "Eunomia"
4 | s.version = "3.8.2"
5 | s.summary = "OSX and iOS common functionalities"
6 | s.license = "MIT"
7 |
8 | s.homepage = "https://github.com/Adorkable/Eunomia"
9 |
10 | s.author = { "Ian G" => "yo.ian.g@gmail.com" }
11 |
12 | s.ios.deployment_target = '10.0'
13 | s.osx.deployment_target = '10.13'
14 |
15 | s.source = { :git => "https://github.com/Adorkable/Eunomia.git", :tag => s.version.to_s }
16 |
17 | s.requires_arc = true
18 |
19 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5' }
20 | s.swift_version = '5.0'
21 |
22 | s.default_subspecs = 'Core'
23 |
24 | s.subspec 'Core' do |core|
25 | core.source_files = "Source/Core/**/*.{h,m,swift}"
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/CoreData/NSManagedObjectContext+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSManagedObjectContext+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 7/24/15.
6 | // Copyright (c) 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | import CoreData
12 |
13 | extension NSManagedObjectContext
14 | {
15 | public func saveOrLogError(_ logContext : String) -> Bool
16 | {
17 | let result : NSError?
18 | do {
19 |
20 | try self.save()
21 | result = nil
22 | } catch let error as NSError {
23 |
24 | Log.error(message: "\(logContext): \(error)")
25 | result = error
26 | }
27 |
28 | return result == nil
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/CNContactStore+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CNContactStore+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | import Contacts
12 |
13 | @available(iOS 9.0, *)
14 | extension CNContactStore {
15 |
16 | // TODO: SuccessResult?
17 | public class func requestAccessToContacts(_ completion: ((_ granted : Bool, _ error : Error?) -> Void)?) {
18 | let contactStore = CNContactStore()
19 |
20 | contactStore.requestAccess(for: CNEntityType.contacts) { (granted, error) -> Void in
21 |
22 | if let completion = completion
23 | {
24 | completion(granted, error)
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Tests/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 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Date+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 6/23/17.
6 | // Copyright © 2017 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Date
12 | {
13 | public func asFileName(_ fileExtension : String, includeMilliseconds : Bool) -> String
14 | {
15 | let formatter = DateFormatter()
16 | formatter.dateFormat = "yyyy-MM-dd_hh-mm-ss"
17 | if includeMilliseconds == true
18 | {
19 | formatter.dateFormat = formatter.dateFormat + "-SSS"
20 | }
21 | return formatter.string(from: self) + ".\(fileExtension)"
22 | }
23 | }
24 |
25 | public func - (left: Date, right: Date) -> TimeInterval {
26 | return left.timeIntervalSince1970 - right.timeIntervalSince1970
27 | }
28 |
--------------------------------------------------------------------------------
/CocoapodTests/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - CocoaLumberjack (2.2.0):
3 | - CocoaLumberjack/Default (= 2.2.0)
4 | - CocoaLumberjack/Extensions (= 2.2.0)
5 | - CocoaLumberjack/Core (2.2.0)
6 | - CocoaLumberjack/Default (2.2.0):
7 | - CocoaLumberjack/Core
8 | - CocoaLumberjack/Extensions (2.2.0):
9 | - CocoaLumberjack/Default
10 | - Eunomia (3.0.3):
11 | - Eunomia/Core (= 3.0.3)
12 | - Eunomia/Core (3.0.3):
13 | - CocoaLumberjack
14 |
15 | DEPENDENCIES:
16 | - CocoaLumberjack (~> 2.2.0)
17 | - Eunomia (from `../`)
18 |
19 | EXTERNAL SOURCES:
20 | Eunomia:
21 | :path: "../"
22 |
23 | SPEC CHECKSUMS:
24 | CocoaLumberjack: 17fe8581f84914d5d7e6360f7c70022b173c3ae0
25 | Eunomia: 481a95a9da7531cfc2cc41372a4aa583c4550e03
26 |
27 | PODFILE CHECKSUM: 2eaf3db9768a4a1d45718e15dbc75f48c0f53168
28 |
29 | COCOAPODS: 1.0.1
30 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/JSContext+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JSContext+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import JavaScriptCore
10 |
11 | extension JSContext {
12 |
13 | // public func setConsoleLogHandler(_ handler : @escaping @convention(block)(String) -> String) {
14 | // self.setObject(unsafeBitCast(handler, to: AnyObject.self), forKeyedSubscript: "log" as NSCopying & NSObjectProtocol)
15 | //
16 | // // TODO: test if console already exists before creating each time
17 | // self.evaluateScript("var console = new Object();")
18 | //
19 | // self.objectForKeyedSubscript("console").setObject(unsafeBitCast(handler, to: AnyObject.self), forKeyedSubscript: "log" as NSCopying & NSObjectProtocol)
20 | // }
21 | }
22 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/CLLocationDirection+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLLocationDirection+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 9/22/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreLocation
11 |
12 | extension CLLocationDirection {
13 | public init(from: Measurement) {
14 | self = from.converted(to: .degrees).value
15 | }
16 |
17 | public init(fromRadians from: Double) {
18 | self = Measurement(value: from, unit: UnitAngle.radians).converted(to: .degrees).value
19 | }
20 |
21 | public var inDegrees: Measurement {
22 | return Measurement(value: self, unit: UnitAngle.degrees)
23 | }
24 |
25 | public var inRadians: Measurement {
26 | return self.inDegrees.converted(to: .radians)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/KeyedDecodingContainer+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyedDecodingContainer.swift
3 | // GTFSKit-iOS
4 | //
5 | // Created by Ian Grossberg on 9/16/18.
6 | // Copyright © 2018 Jack Wilsdon. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension KeyedDecodingContainer {
12 | public func decode(_ type: Date.Type, forKey key: KeyedDecodingContainer.Key, formatter: DateFormatter) throws -> Date {
13 | let stringValue = try self.decode(String.self, forKey: key)
14 |
15 | guard let result = formatter.date(from: stringValue) else {
16 | throw DecodingError.dataCorrupted(DecodingError.Context(
17 | codingPath: [key],
18 | debugDescription: "Invalid time format, expected '\(String(describing: formatter.dateFormat))', received value '\(stringValue)'")
19 | )
20 | }
21 | return result
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Source/Framework/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 3.8.2
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Source/Core/Misc/System.swift:
--------------------------------------------------------------------------------
1 | //
2 | // System.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 7/29/15.
6 | // Copyright (c) 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // TODO: vs NSFileManager.defaultManager().URLsForDirectory?
12 | // TODO: UIApplicationExtension?
13 | public func getAppDocumentPaths() -> [String]? {
14 | return NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
15 | }
16 |
17 | public func getAppDocumentPath() -> String? {
18 | return getAppDocumentPaths()?.first
19 | }
20 |
21 | public func getAppLibraryPaths() -> [String]? {
22 | return NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
23 | }
24 |
25 | public func getAppLibraryPath() -> String? {
26 | return getAppLibraryPaths()?.first
27 | }
28 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/NSCharacterSet+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSCharacterSet+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension CharacterSet {
12 |
13 | // TODO: ascending and descending
14 | public var charactersInSetAscending : String {
15 | var result = String()
16 |
17 | for test in unichar.min...unichar.max {
18 | if let unicodeValue = UnicodeScalar(test) {
19 | if self.contains(unicodeValue) {
20 |
21 | var found = test
22 | withUnsafePointer(to: &found, { (testPointer) -> Void in
23 | let append = NSString(characters: testPointer, length: 1)
24 | result = result + (append as String)
25 | })
26 | }
27 | }
28 | }
29 |
30 | return result
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Tests/NSCharacterSet+UtilityTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSCharacterSet+UtilityTests.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | import Eunomia
12 |
13 | class NSCharacterSet_UtilityTests: XCTestCase {
14 |
15 | // TODO: test all characters
16 | func testStringWithCharactersInSetAscending() {
17 | // "The longest words with no repeated letters are dermatoglyphics, misconjugatedly and uncopyrightables."
18 |
19 | let characters = "misconjugatedly"
20 | let set = CharacterSet(charactersIn: characters)
21 |
22 | let charactersSortedArray = characters.sorted()
23 | let charactersSorted = String(charactersSortedArray)
24 |
25 | XCTAssertEqual(set.charactersInSetAscending, charactersSorted, "Returned characters in set \"\(set.charactersInSetAscending)\" are not equal to expected characters in set \"\(charactersSorted)\"")
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/RangeReplaceableCollectionType+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RangeReplaceableCollectionType+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // http://stackoverflow.com/a/30724543
12 | extension RangeReplaceableCollection where Iterator.Element : Equatable {
13 |
14 | // Remove first collection element that is equal to the given `object`:
15 | mutating func removeObject(_ object : Iterator.Element) {
16 | if let index = self.firstIndex(of: object) {
17 | self.remove(at: index)
18 | }
19 | }
20 |
21 | // Remove first collection element that is equal to the given `object`:
22 | mutating func removeObjects(_ objects : [Iterator.Element]) {
23 | for object in objects {
24 | if let index = self.firstIndex(of: object) {
25 | self.remove(at: index)
26 | }
27 | }
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/Source/Core/Misc/Tangent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tangent.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 9/19/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | public func tangent(from fromCenter: CGPoint, withRadius fromRadius: CGFloat, to toCenter: CGPoint, withRadius toRadius: CGFloat) -> CGPoint {
13 | // Based on http://www.ambrsoft.com/TrigoCalc/Circles2/Circles2Tangent_.htm
14 | let result: CGPoint
15 | if fromRadius == toRadius {
16 | // TODO: hack
17 | result = CGPoint(
18 | x: fromCenter.x + (toCenter.x - fromCenter.x) / 2 - fromRadius / 2,
19 | y: fromCenter.y + (toCenter.y - fromCenter.y) / 2 - fromRadius / 2
20 | )
21 | } else {
22 | result = CGPoint(
23 | x: (toCenter.x * fromRadius - fromCenter.x * toRadius) / (fromRadius - toRadius),
24 | y: (toCenter.y * fromRadius - fromCenter.y * toRadius) / (fromRadius - toRadius)
25 | )
26 | }
27 |
28 | return result
29 | }
30 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/NSDataAsset.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSDataAsset.swift
3 | // TracksLibrary
4 | //
5 | // Created by Ian Grossberg on 9/27/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 | #elseif os(macOS)
12 | import Cocoa
13 | #endif
14 |
15 | extension NSDataAsset {
16 | public enum Errors: Error, CustomStringConvertible {
17 | case assetNotFound(name: String, bundle: Bundle)
18 |
19 | public var description: String {
20 | switch self {
21 | case .assetNotFound(let name, let bundle):
22 | return "Asset '\(name)' not found in bundle '\(bundle)'"
23 | }
24 | }
25 | }
26 | }
27 |
28 | extension NSDataAsset {
29 | public static func create(name: String, bundle: Bundle) throws -> NSDataAsset {
30 | guard let result = NSDataAsset(name: name, bundle: bundle) else {
31 | throw NSDataAsset.Errors.assetNotFound(name: name, bundle: bundle)
32 | }
33 | return result
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2015 Ian Grossberg
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/MPMediaItem+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MPMediaItem+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 9/20/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import MediaPlayer
10 |
11 | #if os(iOS)
12 | extension MPMediaItem {
13 | public static func sortByPlaybackDuration(left: MPMediaItem, right: MPMediaItem) -> Bool {
14 | let comparison: ComparisonResult = self.sortByPlaybackDuration(left: left, right: right)
15 | return comparison == .orderedDescending
16 | }
17 |
18 | public static func sortByPlaybackDuration(left: MPMediaItem, right: MPMediaItem) -> ComparisonResult {
19 | if left.playbackDuration < right.playbackDuration {
20 | return .orderedDescending
21 | }
22 | if left.playbackDuration == right.playbackDuration {
23 | return .orderedSame
24 | }
25 | return .orderedAscending
26 | }
27 | }
28 |
29 | extension Array where Element == MPMediaItem {
30 | public func sortedByPlaybackDuration() -> [MPMediaItem] {
31 | return self.sorted(by: MPMediaItem.sortByPlaybackDuration)
32 | }
33 | }
34 | #endif
35 |
--------------------------------------------------------------------------------
/Tests/SystemTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SystemTests.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 7/29/15.
6 | // Copyright (c) 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 |
12 | import Eunomia
13 |
14 | class SystemTests: XCTestCase {
15 |
16 | func testGetAppDocumentPaths() {
17 | let paths = getAppDocumentPaths()
18 |
19 | XCTAssertNotNil(paths, "Result should not be nil")
20 | XCTAssertGreaterThan(paths!.count, 0, "Result should include at least one path")
21 | }
22 |
23 | func testGetAppDocumentPath() {
24 | let path = getAppDocumentPath()
25 |
26 | XCTAssertNotNil(path, "Result should not be nil")
27 | }
28 |
29 | func testGetAppLibraryPaths() {
30 | let paths = getAppLibraryPaths()
31 |
32 | XCTAssertNotNil(paths, "Result should not be nil")
33 | XCTAssertGreaterThan(paths!.count, 0, "Result should include at least one path")
34 |
35 | }
36 |
37 | func testGetAppLibraryPath() {
38 | let path = getAppLibraryPath()
39 |
40 | XCTAssertNotNil(path, "Result should not be nil")
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UIViewController+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | public extension UIViewController {
13 |
14 | func forceLoadView() {
15 | let _ = self.view
16 | }
17 |
18 | func childViewControllers(_ ofClass : AnyClass) -> [UIViewController]? {
19 | var result : [UIViewController]?
20 |
21 | let filteredChildren = self.children.filter( { $0.isKind(of: ofClass) } )
22 | if filteredChildren.count > 0
23 | {
24 | result = filteredChildren
25 | }
26 |
27 | return result
28 | }
29 |
30 | @IBAction func popViewController() {
31 | self.navigationController?.popViewController(animated: true)
32 | }
33 | }
34 |
35 | public extension UIViewController {
36 | @IBAction func dismissSelfAnimated() {
37 | self.dismiss(animated: true, completion: nil)
38 | }
39 |
40 | @IBAction func dismissSelf() {
41 | self.dismiss(animated: false, completion: nil)
42 | }
43 | }
44 |
45 | #endif
46 |
--------------------------------------------------------------------------------
/Source/Core/App/AppExtensionSafeApplication.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppExtensionSafeApplication.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 6/27/19.
6 | // Copyright © 2019 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | // Based on https://github.com/heilerich/Gestalt/commit/259c4f2e3e0878e97ee2b98cad2610990a3e2f14
13 | #if os(iOS)
14 | public class AppExtensionSafeApplication {
15 | static var shared: UIApplication? {
16 | let sharedSelector = NSSelectorFromString("sharedApplication") // Whadda hack
17 | guard UIApplication.responds(to: sharedSelector) else {
18 | // Extensions cannot access UIApplication
19 | return nil
20 | }
21 | let shared = UIApplication.perform(sharedSelector)
22 | return shared?.takeUnretainedValue() as? UIApplication
23 | }
24 |
25 | static func sharedThrowing() throws -> UIApplication {
26 | guard let shared = self.shared else {
27 | throw Errors.noApplicationAvailable
28 | }
29 |
30 | return shared
31 | }
32 | }
33 |
34 | public extension AppExtensionSafeApplication {
35 | enum Errors: Error {
36 | case noApplicationAvailable
37 | }
38 | }
39 | #endif
40 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UIWebView+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIWebView+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | import JavaScriptCore
13 |
14 | extension UIWebView {
15 |
16 | public var jsContext : JSContext? {
17 | return self.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as? JSContext
18 | }
19 |
20 | public func loadFullCanvasHTML() {
21 | let html =
22 | "\n" +
23 | "\n" +
24 | " \n" +
25 | " \n" +
31 | " \n" +
32 | " \n" +
33 | " \n" +
35 | " \n" +
36 | ""
37 |
38 | self.loadHTMLString(html, baseURL: nil)
39 | }
40 | }
41 | #endif
42 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UITextField+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITextField+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UITextField {
13 |
14 | func setErrorMode(_ enabled : Bool, nonErrorColor : UIColor = UIColor.clear, nonErrorCornerRadius : CGFloat = 0, errorColor : UIColor = UIColor(red: 1, green: 0, blue: 0, alpha: 0.09), errorCornerRadius : CGFloat = 2) {
15 |
16 | if enabled == true {
17 |
18 | self.backgroundColor = errorColor
19 | self.cornerRadius = errorCornerRadius
20 | } else {
21 |
22 | self.backgroundColor = nonErrorColor
23 | self.cornerRadius = nonErrorCornerRadius
24 | }
25 | }
26 | }
27 |
28 | extension UITextView {
29 | public func verticallyCenterContents() {
30 | let surroundingSpace = self.bounds.size.height - self.contentSize.height
31 |
32 | let inset: CGFloat
33 | if surroundingSpace > 0 {
34 | inset = max(0, surroundingSpace / 2.0)
35 | } else {
36 | inset = 0
37 | }
38 | self.contentInset = UIEdgeInsets(top: inset, left: self.contentInset.left, bottom: inset, right: self.contentInset.right)
39 | }
40 | }
41 | #endif
42 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UITabBar+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITabBar+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UITabBar {
13 |
14 | public func tabBarButtonViews() throws -> [UIView]? {
15 |
16 | let tabBarButtonClassName = "UITabBarButton"
17 | guard let tabBarButtonClass = NSClassFromString(tabBarButtonClassName) else {
18 | throw NSError(description: "Unable to retrieve instance of \(tabBarButtonClassName)")
19 | }
20 |
21 | var result : [UIView]?
22 |
23 | for view in self.subviews {
24 |
25 | if view.isKind(of: tabBarButtonClass) {
26 |
27 | if result == nil {
28 | result = [UIView]()
29 | }
30 |
31 | guard result != nil else {
32 | throw NSError(description: "Unable to allocate array for storage of Tab Bar Button Views")
33 | }
34 |
35 | result!.append(view)
36 | }
37 | }
38 |
39 | if result != nil {
40 | result?.sort { $0.frame.origin.x < $1.frame.origin.x }
41 | }
42 |
43 | return result
44 | }
45 |
46 | }
47 | #endif
48 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UIProgressView+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIProgressView+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 6/23/17.
6 | // Copyright © 2017 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UIProgressView {
13 | @available(iOS 10.0, *)
14 | public func scheduleUpdater(withTimeInterval timeInterval: TimeInterval, updater: @escaping (() -> Float), invalidatesWhen: @escaping ((_ progress: Float) -> Bool)) {
15 |
16 | Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true) { (timer) in
17 |
18 | self.progress = updater()
19 |
20 | if invalidatesWhen(self.progress) {
21 | timer.invalidate()
22 | }
23 | }
24 | }
25 |
26 | @available(iOS 10.0, *)
27 | public func autoprogress(from: Date, to: Date, finishEarlyCondition finishEarly: @escaping (() -> Bool)) {
28 |
29 | // TODO: throw if from > to
30 | // TODO: ability to specify time interval
31 |
32 | self.scheduleUpdater(withTimeInterval: 0.001, updater: { () -> Float in
33 | let result: Float
34 |
35 | let difference = to - from
36 | let nowDifference = Date() - from
37 |
38 | if difference == 0 {
39 | result = 0
40 | } else {
41 | result = Float(nowDifference / difference)
42 | }
43 | return result
44 | }) { (progress) -> Bool in
45 |
46 | if finishEarly() {
47 | return true
48 | }
49 | return progress >= 1
50 | }
51 | }
52 | }
53 | #endif
54 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/NSAttributedString+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSAttributedString+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension NSAttributedString {
13 | // http://stackoverflow.com/a/21901056/96153
14 | convenience init(htmlString: String) throws {
15 |
16 | guard let data = htmlString.data() else {
17 | throw NSError(description: "Failed to create \(type(of: self))", underlyingError: NSError(description: "Unable to translate htmlString into NSData"))
18 | }
19 |
20 | try self.init(data: data as Data, options: [
21 | .documentType : NSAttributedString.DocumentType.html as AnyObject,
22 | .characterEncoding : String.Encoding.utf8 as AnyObject
23 | ], documentAttributes: nil)
24 | }
25 | }
26 |
27 | extension NSAttributedString {
28 | // https://stackoverflow.com/a/30450559
29 | func height(withConstrainedWidth width: CGFloat) -> CGFloat {
30 | let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
31 | let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
32 |
33 | return ceil(boundingBox.height)
34 | }
35 |
36 | func width(withConstrainedHeight height: CGFloat) -> CGFloat {
37 | let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
38 | let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)
39 |
40 | return ceil(boundingBox.width)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/DispatchQueue+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DispatchQueue+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 6/8/17.
6 | // Copyright © 2017 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension DispatchQueue {
12 |
13 | /**
14 | Executes a block of code only once. The code is thread safe and will
15 | only execute the code once even in the presence of multithreaded calls.
16 |
17 | - parameter block: Block to execute once
18 | */
19 | class func once(block: () -> Void) {
20 | self.once(token: String.randomString(64), block: block)
21 | }
22 |
23 | // From https://stackoverflow.com/a/38311178/96153
24 | private static var _onceTracker = [String]()
25 |
26 | /**
27 | Executes a block of code, associated with a unique token, only once. The code is thread safe and will
28 | only execute the code once even in the presence of multithreaded calls.
29 |
30 | - parameter token: A unique reverse DNS style name such as com.vectorform. or a GUID
31 | - parameter block: Block to execute once
32 | */
33 | class func once(token: String, block: () -> Void) {
34 | objc_sync_enter(self); defer { objc_sync_exit(self) }
35 |
36 | if _onceTracker.contains(token) {
37 | return
38 | }
39 |
40 | _onceTracker.append(token)
41 | block()
42 | }
43 | }
44 |
45 | public func dispatch_async_main(_ closure : @escaping () -> Void) {
46 | DispatchQueue.main.async(execute: closure)
47 | }
48 |
49 | public func dispatch_async_background(_ closure : @escaping () -> Void) {
50 | DispatchQueue.global(qos: .background).async {
51 | closure()
52 | }
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Array+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Array {
12 | public mutating func appendIfPresent(_ newElement : Element?) {
13 | if let newElement = newElement {
14 | self.append(newElement)
15 | }
16 | }
17 | }
18 |
19 | extension Array {
20 | public var randomElement: Element? {
21 | guard self.count > 0 else {
22 | return nil
23 | }
24 | let index = Int.random(in: 0...(self.count - 1))
25 | return self[index]
26 | }
27 | }
28 |
29 | extension Array {
30 | public subscript(index: UInt) -> Element? {
31 | // TODO: overflow protection
32 | return self[Int(index)]
33 | }
34 | }
35 |
36 | extension Array {
37 | public enum Errors: Error {
38 | case noMatchesFoundError
39 | case tooManyMatchesFoundError(matches: [Element])
40 | }
41 |
42 | public func filterOne(_ isIncluded: (Element) throws -> Bool) throws -> Element {
43 | let found = try self.filter(isIncluded)
44 |
45 | guard found.count < 2 else {
46 | throw Errors.tooManyMatchesFoundError(matches: found)
47 | }
48 |
49 | guard let result = found.first else {
50 | throw Errors.noMatchesFoundError
51 | }
52 |
53 | return result
54 | }
55 | }
56 |
57 | public extension Array where Element: NSAttributedString {
58 | func toAttributedString() -> NSAttributedString {
59 | return self.reduce(NSMutableAttributedString(string: "")) { (accumulated, next) -> NSMutableAttributedString in
60 | accumulated.append(NSAttributedString(string: " "))
61 | accumulated.append(next)
62 | return accumulated
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UICollectionViewFlowLayout+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionViewFlowLayout+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UICollectionViewFlowLayout {
13 |
14 | // return the number of items in width and height dimensions to fit the specified width
15 | public func numberOfItemsToFit(_ width : CGFloat, itemCount : Int) -> CGSize {
16 | var result = CGSize(width: 0, height: 0)
17 |
18 | var usedWidth = width
19 | while usedWidth > 0
20 | {
21 | if result.width > 0
22 | {
23 | usedWidth -= self.minimumInteritemSpacing
24 | if width >= self.itemSize.width
25 | {
26 | result.width += 1
27 | usedWidth -= self.itemSize.width
28 | } else
29 | {
30 | break
31 | }
32 | } else
33 | {
34 | result.width += 1
35 | usedWidth -= self.itemSize.width
36 | }
37 | }
38 |
39 | result.height = CGFloat(itemCount / Int(result.width) )
40 | if itemCount % Int(result.width) > 0
41 | {
42 | result.height += 1
43 | }
44 |
45 | return result
46 | }
47 |
48 | public func heightForItemsToFit(_ width : CGFloat, itemCount : Int) -> CGFloat {
49 |
50 | var result : CGFloat
51 |
52 | if itemCount > 0
53 | {
54 | let numberOfItems = self.numberOfItemsToFit(width, itemCount: itemCount)
55 |
56 | result = CGFloat(numberOfItems.height) * self.itemSize.height
57 | if numberOfItems.height > 1
58 | {
59 | result += self.minimumLineSpacing * CGFloat(numberOfItems.height - 1)
60 | }
61 | } else
62 | {
63 | result = 0
64 | }
65 |
66 | return result
67 | }
68 | }
69 | #endif
70 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.0)
5 | activesupport (4.2.11.1)
6 | i18n (~> 0.7)
7 | minitest (~> 5.1)
8 | thread_safe (~> 0.3, >= 0.3.4)
9 | tzinfo (~> 1.1)
10 | atomos (0.1.3)
11 | claide (1.0.2)
12 | cocoapods (1.7.2)
13 | activesupport (>= 4.0.2, < 5)
14 | claide (>= 1.0.2, < 2.0)
15 | cocoapods-core (= 1.7.2)
16 | cocoapods-deintegrate (>= 1.0.3, < 2.0)
17 | cocoapods-downloader (>= 1.2.2, < 2.0)
18 | cocoapods-plugins (>= 1.0.0, < 2.0)
19 | cocoapods-search (>= 1.0.0, < 2.0)
20 | cocoapods-stats (>= 1.0.0, < 2.0)
21 | cocoapods-trunk (>= 1.3.1, < 2.0)
22 | cocoapods-try (>= 1.1.0, < 2.0)
23 | colored2 (~> 3.1)
24 | escape (~> 0.0.4)
25 | fourflusher (>= 2.3.0, < 3.0)
26 | gh_inspector (~> 1.0)
27 | molinillo (~> 0.6.6)
28 | nap (~> 1.0)
29 | ruby-macho (~> 1.4)
30 | xcodeproj (>= 1.10.0, < 2.0)
31 | cocoapods-core (1.7.2)
32 | activesupport (>= 4.0.2, < 6)
33 | fuzzy_match (~> 2.0.4)
34 | nap (~> 1.0)
35 | cocoapods-deintegrate (1.0.4)
36 | cocoapods-downloader (1.2.2)
37 | cocoapods-plugins (1.0.0)
38 | nap
39 | cocoapods-search (1.0.0)
40 | cocoapods-stats (1.1.0)
41 | cocoapods-trunk (1.3.1)
42 | nap (>= 0.8, < 2.0)
43 | netrc (~> 0.11)
44 | cocoapods-try (1.1.0)
45 | colored2 (3.1.2)
46 | concurrent-ruby (1.1.5)
47 | escape (0.0.4)
48 | fourflusher (2.3.1)
49 | fuzzy_match (2.0.4)
50 | gh_inspector (1.1.3)
51 | i18n (0.9.5)
52 | concurrent-ruby (~> 1.0)
53 | minitest (5.11.3)
54 | molinillo (0.6.6)
55 | nanaimo (0.2.6)
56 | nap (1.1.0)
57 | netrc (0.11.0)
58 | ruby-macho (1.4.0)
59 | thread_safe (0.3.6)
60 | tzinfo (1.2.5)
61 | thread_safe (~> 0.1)
62 | xcodeproj (1.10.0)
63 | CFPropertyList (>= 2.3.3, < 4.0)
64 | atomos (~> 0.1.3)
65 | claide (>= 1.0.2, < 2.0)
66 | colored2 (~> 3.1)
67 | nanaimo (~> 0.2.6)
68 |
69 | PLATFORMS
70 | ruby
71 |
72 | DEPENDENCIES
73 | cocoapods
74 |
75 | BUNDLED WITH
76 | 1.16.2
77 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UICollectionView+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionView+Utility.swift
3 | // Allergy Abroad -> Eunomia
4 | //
5 | // Created by Ian Grossberg on 7/6/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | public extension UICollectionView {
13 | func isLastCellVisible(inSection: Int) -> Bool {
14 | let lastIndexPath = IndexPath(row: self.numberOfItems(inSection: inSection) - 1, section: inSection)
15 | guard let cellAttributes = self.layoutAttributesForItem(at: lastIndexPath) else {
16 | // TODO:
17 | // throw
18 | return false
19 | }
20 | let cellRect = self.convert(cellAttributes.frame, to: self.superview)
21 |
22 | var visibleRect = CGRect(
23 | x: self.bounds.origin.x,
24 | y: self.bounds.origin.y,
25 | width: self.bounds.size.width,
26 | height: self.bounds.size.height - self.contentInset.bottom
27 | )
28 | visibleRect = self.convert(visibleRect, to: self.superview)
29 |
30 | if visibleRect.intersects(cellRect) || visibleRect.contains(cellRect) {
31 | return true
32 | }
33 |
34 | return false
35 | }
36 |
37 | func isLastCellFullyVisible(inSection: Int) -> Bool {
38 | let lastIndexPath = IndexPath(row: self.numberOfItems(inSection: inSection) - 1, section: inSection)
39 | guard let cellAttributes = self.layoutAttributesForItem(at: lastIndexPath) else {
40 | // TODO:
41 | // throw
42 | return false
43 | }
44 | let cellRect = self.convert(cellAttributes.frame, to: self.superview)
45 |
46 | var visibleRect = CGRect(
47 | x: self.bounds.origin.x,
48 | y: self.bounds.origin.y,
49 | width: self.bounds.size.width,
50 | height: self.bounds.size.height - self.contentInset.bottom
51 | )
52 | visibleRect = self.convert(visibleRect, to: self.superview)
53 |
54 | if visibleRect.contains(cellRect) {
55 | return true
56 | }
57 |
58 | return false
59 | }
60 | }
61 | #endif
62 |
--------------------------------------------------------------------------------
/CarthageTests/Eunomia.xcodeproj/xcshareddata/xcschemes/EunomiaCocoapodiOSTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
39 |
40 |
41 |
42 |
48 |
49 |
51 |
52 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/CocoapodTests/Eunomia.xcodeproj/xcshareddata/xcschemes/EunomiaCocoapodiOSTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
39 |
40 |
41 |
42 |
48 |
49 |
51 |
52 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UIScrollView+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIScrollView+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 10/5/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UIScrollView {
13 |
14 | fileprivate var filteredScrollLastContentOffsetKey : String {
15 | return "Eunomia_UIScrollView_filteredScrollLastContentOffsetKey"
16 | }
17 | fileprivate var filteredScrollLastContentOffset : CGFloat {
18 | get {
19 | return self.getAssociatedProperty(self.filteredScrollLastContentOffsetKey, fallback: 0)
20 | }
21 | set {
22 | self.setAssociatedRetainProperty(self.filteredScrollLastContentOffsetKey, value: newValue as AnyObject)
23 | }
24 | }
25 |
26 | public func applyFilteredScrollForScrollingBar(_ bar : UIView, barTopAlignment : NSLayoutConstraint, minimumDelta : CGFloat = 20) {
27 |
28 | let scrollDistance : CGFloat = self.contentOffset.y - self.filteredScrollLastContentOffset
29 |
30 | self.filteredScrollLastContentOffset = self.contentOffset.y
31 |
32 | // Minimum delta scroll before bar is effected, ie: only adjust past a minimum scroll speed. We allow even small amounts to pass if we're in the middle of hiding or showing the bar.
33 | guard abs(scrollDistance) > abs(minimumDelta) || (barTopAlignment.constant != 0 && barTopAlignment.constant != -bar.frame.height) else {
34 | return
35 | }
36 |
37 | // Only allow the topbar to move into and out of the area it's in and above
38 | guard barTopAlignment.constant - scrollDistance <= 0 else {
39 |
40 | barTopAlignment.constant = 0
41 | return
42 | }
43 |
44 | // Only move the topbar enough to hide it
45 | guard abs(barTopAlignment.constant - scrollDistance) <= bar.frame.height else {
46 |
47 | barTopAlignment.constant = -bar.frame.height
48 | return
49 | }
50 |
51 | barTopAlignment.constant -= scrollDistance / 2
52 | bar.superview?.setNeedsLayout()
53 | }
54 | }
55 | #endif
56 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/CoreData/NSFetchedResultsController+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSFetchedResultsController+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 7/29/15.
6 | // Copyright (c) 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | import CoreData
12 |
13 | // Currently bugged: https://bugs.swift.org/browse/SR-2708
14 | //extension NSFetchedResultsController {
15 | //
16 | // convenience init(fetchObjectType : NSManagedObject.Type, managedObjectContext context : NSManagedObjectContext, sectionNameKeyPath : String? = nil, cacheName : String? = nil) {
17 | //
18 | // let useFetchRequest = fetchObjectType.guaranteeFetchRequest(nil)
19 | // self.init(fetchRequest: useFetchRequest as! NSFetchRequest<_>, managedObjectContext: context, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName)
20 | // }
21 | //
22 | // public func firstResultOfPerformFetch() throws -> AnyObject? {
23 | // var result : AnyObject?
24 | //
25 | // try self.performFetch()
26 | //
27 | // if let fetchedObjects = self.fetchedObjects
28 | // {
29 | // result = fetchedObjects.first
30 | // }
31 | //
32 | // return result
33 | // }
34 | //
35 | // public class func firstResult(_ fetchRequest : NSFetchRequest, context : NSManagedObjectContext, sectionNameKeyPath : String? = nil, cacheName : String? = nil) throws -> AnyObject? {
36 | //
37 | // let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest as! NSFetchRequest, managedObjectContext: context, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName)
38 | // return try fetchedResultsController.firstResultOfPerformFetch()
39 | // }
40 | //
41 | // public var fetchedObjectsCount : Int {
42 | // return guarantee(self.fetchedObjects?.count, fallback: 0)
43 | // }
44 | //
45 | // public class func fetchedObjectsCount(_ fetchObjectType : NSManagedObject.Type, context : NSManagedObjectContext, sectionNameKeyPath : String? = nil, cacheName : String? = nil) throws -> Int {
46 | //
47 | // let fetchedResultsController = NSFetchedResultsController(fetchObjectType: fetchObjectType, managedObjectContext: context, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName)
48 | // try fetchedResultsController.performFetch()
49 | //
50 | // return fetchedResultsController.fetchedObjectsCount
51 | // }
52 | //}
53 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/NSError+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSError+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public let NSFunctionErrorKey = "FunctionError"
12 | public let NSLineErrorKey = "LineError"
13 |
14 | extension NSError {
15 |
16 | // TODO: support NSLocalizedString
17 | // TODO: support Recovery Attempter
18 | // TODO: relevant object
19 | public static func userInfo(_ description : String, underlyingError : NSError? = nil, failureReason : String? = nil, recoverySuggestion : String? = nil, recoveryOptions : [String]? = nil, fileName : String = #file, functionName : String = #function, line : Int = #line) -> [String : AnyObject] {
20 |
21 | var userInfo = [
22 | NSFilePathErrorKey : fileName,
23 | NSFunctionErrorKey : functionName,
24 | NSLineErrorKey : NSNumber(value: line as Int),
25 |
26 | NSLocalizedDescriptionKey : description
27 | ] as [String : Any]
28 |
29 | if let underlyingError = underlyingError {
30 | userInfo[NSUnderlyingErrorKey] = underlyingError
31 | }
32 |
33 | if let failureReason = failureReason {
34 | userInfo[NSLocalizedFailureReasonErrorKey] = failureReason
35 | }
36 |
37 | if let recoverySuggestion = recoverySuggestion {
38 | userInfo[NSLocalizedRecoverySuggestionErrorKey] = recoverySuggestion
39 | }
40 |
41 | if let recoveryOptions = recoveryOptions {
42 | userInfo[NSLocalizedRecoveryOptionsErrorKey] = recoveryOptions
43 | }
44 |
45 | return userInfo as [String : AnyObject]
46 | }
47 |
48 | public convenience init(domain : String? = nil, code : Int = 0, description : String, underlyingError : NSError? = nil, failureReason : String? = nil, recoverySuggestion : String? = nil, recoveryOptions : [String]? = nil, fileName : String = #file, functionName : String = #function, line : Int = #line) {
49 |
50 | let useDomain : String
51 | if let domain = domain {
52 | useDomain = domain
53 | } else {
54 | useDomain = description
55 | }
56 |
57 | let userInfo = NSError.userInfo(description, underlyingError: underlyingError, failureReason: failureReason, recoverySuggestion: recoverySuggestion, recoveryOptions: recoveryOptions, fileName: fileName, functionName: functionName, line: line)
58 |
59 | self.init(domain: useDomain, code: 0, userInfo: userInfo)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Tests/MeasurementTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MeasurementTest.swift
3 | // TracksTests
4 | //
5 | // Created by Ian Grossberg on 8/22/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import Eunomia
11 |
12 | class MeasurementTest: XCTestCase {
13 | static let testValueCount = 1000
14 |
15 | static var randomUnitLength: UnitLength {
16 |
17 | let unit = [UnitLength](arrayLiteral: .meters,
18 | .decimeters,
19 | .centimeters,
20 | .millimeters,
21 | .micrometers,
22 | .nanometers,
23 | .picometers,
24 | .inches,
25 | .feet,
26 | .yards)//UnitLength.all // TODO: precision is messing this up
27 | .randomElement
28 | XCTAssertNotNil(unit, "randomUnitLength")
29 | if unit == nil {
30 | XCTFail()
31 | }
32 | return unit!
33 | }
34 | static var testValues: [Measurement] {
35 | var result: [Measurement] = []
36 | while result.count < self.testValueCount {
37 | result.append(Measurement(value: Double(arc4random()) / Double(arc4random()), unit: self.randomUnitLength))
38 | }
39 | return result
40 | }
41 |
42 | static func calculateAverage(values: [Measurement]) -> Measurement {
43 | guard values.count > 0 else {
44 | return Measurement(value: 0, unit: UnitLength.furlongs)
45 | }
46 |
47 | let sumUnit = self.randomUnitLength
48 | let destinationUnit = self.randomUnitLength
49 |
50 | var sum = Measurement(value: 0, unit: sumUnit)
51 | for value in values {
52 | sum = sum + value
53 | }
54 | return Measurement(value: sum.converted(to: destinationUnit).value / Double(values.count), unit: destinationUnit)
55 | }
56 |
57 | // func testAverageVArgs() {
58 | // }
59 |
60 | func testAverageArray() {
61 | let staticContext = type(of: self)
62 |
63 | let testValues = staticContext.testValues
64 |
65 | let left = staticContext.calculateAverage(values: testValues)
66 | let right = average(values: testValues)
67 | // XCTAssertEqual(left, right, "Average of test values")
68 | XCTAssertLessThan((left - right).value, 0.000001, "Average value precision difference") // TODO: less than value should be unit aware
69 |
70 | XCTAssertEqual(staticContext.calculateAverage(values: []), average(values: []), "Average of no test values")
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Source/Core/App/EunomiaAppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EunomiaAppDelegate.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 7/23/15.
6 | // Copyright (c) 2015 Eunomia. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | open class EunomiaAppDelegate: UIResponder, UIApplicationDelegate {
13 | open var window: UIWindow?
14 |
15 | open func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 |
17 | do {
18 | try Log.setupLogger(consoleLogLevel: .debug)
19 | } catch {
20 | NSLog("Error: while settings up logging: \(error)")
21 | }
22 |
23 | UIApplication.dumpApplicationInformation()
24 |
25 | return true
26 | }
27 |
28 | open func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
29 | return true
30 | }
31 |
32 | open func applicationDidBecomeActive(_ application: UIApplication) {
33 |
34 | }
35 |
36 | open func applicationWillResignActive(_ application: UIApplication) {
37 |
38 | }
39 |
40 | open func application(_ application: UIApplication, handleOpen url: URL) -> Bool {
41 | return false
42 | }
43 |
44 | open func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
45 | return false
46 | }
47 |
48 | open func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
49 |
50 | }
51 |
52 | open func applicationWillTerminate(_ application: UIApplication) {
53 |
54 | }
55 |
56 | open func applicationSignificantTimeChange(_ application: UIApplication) {
57 |
58 | }
59 |
60 | open func application(_ application: UIApplication, willChangeStatusBarOrientation newStatusBarOrientation: UIInterfaceOrientation, duration: TimeInterval) {
61 |
62 | }
63 |
64 | open func application(_ application: UIApplication, didChangeStatusBarOrientation oldStatusBarOrientation: UIInterfaceOrientation) {
65 |
66 | }
67 |
68 | open func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
69 |
70 | }
71 |
72 | open func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
73 |
74 | }
75 |
76 | open func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
77 |
78 | }
79 |
80 | open func applicationDidEnterBackground(_ application: UIApplication) {
81 |
82 | }
83 |
84 | open func applicationWillEnterForeground(_ application: UIApplication) {
85 |
86 | }
87 | }
88 | #endif
89 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/UIApplication+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIApplication+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 7/24/15.
6 | // Copyright (c) 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UIApplication
13 | {
14 | public class func dumpApplicationInformation() {
15 | if let applicationVersion = self.applicationVersion
16 | {
17 | Log.info(message: "Application Version: \(applicationVersion)")
18 | }
19 | if let buildVersion = self.buildVersion
20 | {
21 | Log.info(message: "Build Version: \(buildVersion)")
22 | }
23 |
24 | if UIDevice.current.isSimulator
25 | {
26 | Log.info(message: "Simulator build running from bundle: \(Bundle.main.bundleURL)")
27 | if let documentPath = getAppDocumentPath()
28 | {
29 | Log.info(message: "Document folder: \(documentPath)")
30 | }
31 | if let appLibraryPath = getAppLibraryPath()
32 | {
33 | Log.info(message: "Library folder: \(appLibraryPath)")
34 | }
35 | } else {
36 | Log.info(message: "Device build")
37 | }
38 | }
39 |
40 | public static var applicationVersion : String? {
41 | var result : String?
42 |
43 | if let info = Bundle.main.infoDictionary
44 | {
45 | result = info["CFBundleShortVersionString"] as? String
46 | }
47 |
48 | return result
49 | }
50 |
51 | public static var buildVersion : String? {
52 | var result : String?
53 |
54 | if let info = Bundle.main.infoDictionary
55 | {
56 | result = info["CFBundleVersion"] as? String
57 | }
58 |
59 | return result
60 | }
61 |
62 | public class func applicationAndBuildVersion() -> String {
63 | return "\(String(describing: self.applicationVersion))_\(String(describing: self.buildVersion))"
64 | }
65 | }
66 |
67 | extension UIApplication {
68 |
69 | public func mailTo(_ emailAddress : String,
70 | subject : String,
71 | body : String? = nil) throws {
72 |
73 | var parameters = [
74 | "mailto:\(emailAddress)",
75 | "subject=\(subject)"
76 | ]
77 | if let body = body {
78 | parameters.append("body=\(body)")
79 | }
80 |
81 | guard let path = (parameters as NSArray).componentsJoined(by: "&").addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) else {
82 | throw NSError(description: "Unable to create path String with parameters \(parameters)")
83 | }
84 |
85 | guard let url = URL(string: path) else {
86 | throw NSError(description: "Unable to create NSURL with path \(path)")
87 | }
88 |
89 | self.open(url, options: [:], completionHandler: nil)
90 | }
91 | }
92 | #endif
93 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/CLLocationCoordinate2D+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLLocationCoordinate2D.swift
3 | // Tracks
4 | //
5 | // Created by Ian Grossberg on 8/22/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreLocation
11 |
12 | extension CLLocationCoordinate2D {
13 | public init(from: (Measurement, Measurement)) {
14 | self.init(latitude: CLLocationDegrees(from: from.0), longitude: CLLocationDegrees(from: from.1))
15 | }
16 |
17 | public init(fromRadians from: (Double, Double)) {
18 | self.init(latitude: CLLocationDegrees(fromRadians: from.0), longitude: CLLocationDegrees(fromRadians: from.1))
19 | }
20 |
21 | public var asMeasurement: (Measurement, Measurement) {
22 | return (
23 | self.latitude.inRadians,
24 | self.longitude.inRadians
25 | )
26 | }
27 |
28 | public var inRadians: (Double, Double) {
29 | let asMeasurement = self.asMeasurement
30 | return (
31 | asMeasurement.0.value,
32 | asMeasurement.1.value
33 | )
34 | }
35 |
36 | public static func -(left: CLLocationCoordinate2D, right: CLLocationCoordinate2D) -> CLLocationCoordinate2D {
37 | return CLLocationCoordinate2D(latitude: left.latitude - right.latitude, longitude: left.longitude - right.longitude)
38 | }
39 | }
40 |
41 | extension CLLocationCoordinate2D {
42 | public func heading(to: CLLocationCoordinate2D) -> Measurement {
43 | let inRadians = (self.latitude.inRadians, self.longitude.inRadians)
44 | let toInRadians = (to.latitude.inRadians, to.longitude.inRadians)
45 |
46 | let delta = (
47 | toInRadians.0 - inRadians.0,
48 | toInRadians.1 - inRadians.1
49 | )
50 |
51 | let y = sin(delta.1.value) * cos(toInRadians.0.value)
52 | let x = cos(inRadians.0.value) * sin(toInRadians.0.value) - sin(inRadians.0.value) * cos(toInRadians.0.value) * cos(delta.1.value)
53 | let heading = atan2(y, x)
54 |
55 | return Measurement(value: heading, unit: UnitAngle.radians)
56 | }
57 |
58 | public func heading(to: CLLocationCoordinate2D) -> CLLocationDirection {
59 | let measurement: Measurement = self.heading(to: to)
60 | return measurement.converted(to: .degrees).value
61 | }
62 | }
63 |
64 | extension CLLocationCoordinate2D {
65 | public func distance(from: CLLocationCoordinate2D) -> CLLocationDistance {
66 | return self.distance(from: CLLocation(coordinate: from))
67 | }
68 |
69 | public func distance(from: CLLocation) -> CLLocationDistance {
70 | return CLLocation(coordinate: self).distance(from: from)
71 | }
72 | }
73 |
74 | extension CLLocationCoordinate2D: Equatable {
75 | public static func == (left: CLLocationCoordinate2D, right: CLLocationCoordinate2D) -> Bool {
76 | return left.latitude == right.latitude
77 | && left.longitude == right.longitude
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/UIFont+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIFont+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian on 5/8/15.
6 | // Copyright (c) 2015 Eunomia. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UIFont {
13 | public class func supportedFonts() -> [String : [String]]? {
14 | var result : [String : [String]]?
15 |
16 | for familyName in UIFont.familyNames {
17 | let fonts = fontNames(forFamilyName: familyName)
18 | if fonts.count > 0 {
19 |
20 | if result == nil {
21 | result = [String : [String]]()
22 | }
23 |
24 | if result != nil {
25 | result![familyName] = fonts
26 | } else {
27 | // TODO: log
28 | }
29 | }
30 | }
31 |
32 | return result
33 | }
34 |
35 | public func fontWithTrait(_ trait : UIFontDescriptor.SymbolicTraits) -> UIFont {
36 | let fontDescriptor = self.fontDescriptor.withSymbolicTraits(trait)
37 | return UIFont(descriptor: fontDescriptor!, size: self.pointSize)
38 | }
39 |
40 | public func regularFont() -> UIFont? {
41 | return UIFont(name: self.fontName, size: self.pointSize)
42 | }
43 |
44 | public func italicFont() -> UIFont {
45 | return self.fontWithTrait(.traitItalic)
46 | }
47 |
48 | public func boldFont() -> UIFont {
49 | return self.fontWithTrait(.traitBold)
50 | }
51 |
52 | public func expandedFont() -> UIFont {
53 | return self.fontWithTrait(.traitExpanded)
54 | }
55 |
56 | public func condensedFont() -> UIFont {
57 | return self.fontWithTrait(.traitCondensed)
58 | }
59 |
60 | public func monospaceFont() -> UIFont {
61 | return self.fontWithTrait(.traitMonoSpace)
62 | }
63 |
64 | public func verticalFont() -> UIFont {
65 | return self.fontWithTrait(.traitVertical)
66 | }
67 |
68 | public func uiOptimizedFont() -> UIFont {
69 | return self.fontWithTrait(.traitUIOptimized)
70 | }
71 |
72 | public func tightLeadingFont() -> UIFont {
73 | return self.fontWithTrait(.traitTightLeading)
74 | }
75 |
76 | public func looseLeadingFont() -> UIFont {
77 | return self.fontWithTrait(.traitLooseLeading)
78 | }
79 | }
80 |
81 | extension UIFont {
82 | // https://stackoverflow.com/a/30450559
83 | public func width(withConstrainedHeight height: CGFloat, string: String) -> CGFloat {
84 | let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
85 | let boundingBox = string.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: self], context: nil)
86 |
87 | return ceil(boundingBox.width)
88 | }
89 |
90 | public func height(withConstrainedWidth width: CGFloat, string: String) -> CGFloat {
91 | let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
92 | let boundingBox = string.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: self], context: nil)
93 |
94 | return ceil(boundingBox.height)
95 | }
96 | }
97 | #endif
98 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Measurement+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Measurement+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 8/22/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension UnitLength {
12 | static var all: [UnitLength] {
13 | return [
14 | .megameters,
15 | .kilometers,
16 | .hectometers,
17 | .decameters,
18 | .meters,
19 | .decimeters,
20 | .centimeters,
21 | .millimeters,
22 | .micrometers,
23 | .nanometers,
24 | .picometers,
25 | .inches,
26 | .feet,
27 | .yards,
28 | .miles,
29 | .scandinavianMiles,
30 | .lightyears,
31 | .nauticalMiles,
32 | .fathoms,
33 | .furlongs,
34 | .astronomicalUnits,
35 | .parsecs
36 | ]
37 | }
38 | }
39 |
40 | public func average(values: Measurement...) -> Measurement {
41 | return average(values: values)
42 | }
43 |
44 | public func average(values: [Measurement]) -> Measurement {
45 | guard values.count > 0 else {
46 | return Measurement(value: 0, unit: UnitLength.furlongs)
47 | }
48 |
49 | let resultUnitType = values[0].unit
50 | let sum = values.reduce(Measurement(value: 0, unit: resultUnitType)) { (accumulated, next) -> Measurement in
51 | return accumulated + next
52 | }
53 |
54 | return Measurement(value: sum.converted(to: resultUnitType).value / Double(values.count), unit: resultUnitType)
55 | }
56 |
57 | public func average(values: Measurement...) -> Measurement {
58 | return average(values: values)
59 | }
60 |
61 | public func average(values: [Measurement]) -> Measurement {
62 | guard values.count > 0 else {
63 | return Measurement(value: 0, unit: UnitSpeed.knots)
64 | }
65 |
66 | let resultUnitType = values[0].unit
67 | let sum = values.reduce(Measurement(value: 0, unit: resultUnitType)) { (accumulated, next) -> Measurement in
68 | return accumulated + next
69 | }
70 |
71 | return Measurement(value: sum.converted(to: resultUnitType).value / Double(values.count), unit: resultUnitType)
72 | }
73 |
74 | public func *(left: Measurement, right: Measurement) -> Measurement {
75 | let valueInMeters = left.converted(to: UnitSpeed.metersPerSecond).value * right.converted(to: UnitDuration.seconds).value
76 | return Measurement(value: valueInMeters, unit: UnitLength.meters)
77 | }
78 |
79 | public func *(left: Measurement, right: Measurement) -> Measurement {
80 | let valueInMeters = left.converted(to: UnitLength.meters).value * right.converted(to: UnitLength.meters).value
81 | return Measurement(value: valueInMeters, unit: UnitLength.meters)
82 | }
83 |
84 | extension Measurement {
85 | public static func `in`(degrees value: Double) -> Measurement {
86 | return Measurement(value: value, unit: UnitAngle.degrees)
87 | }
88 | }
89 |
90 | extension Measurement where UnitType == UnitAngle {
91 | public var radians: Measurement {
92 | return self.converted(to: UnitAngle.radians)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Source/Core/Misc/OrdinalNumberFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrdinalNumberFormatter.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // translated from http://stackoverflow.com/a/3316016
12 | open class OrdinalNumberFormatter: NumberFormatter {
13 |
14 | open override func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer?, for string: String, range rangep: UnsafeMutablePointer?) throws {
15 |
16 | var integerNumber : Int = 0
17 |
18 | let scanner = Scanner(string: string)
19 | scanner.caseSensitive = false
20 | scanner.charactersToBeSkipped = CharacterSet.letters
21 |
22 | if scanner.scanInt(&integerNumber) == true
23 | {
24 | obj?.pointee = NSNumber(value: integerNumber)
25 | } else
26 | {
27 | throw UnableToCreateNumberFromStringError(string: string)
28 | }
29 | }
30 |
31 | override open func string(for obj: Any?) -> String? {
32 | if !(obj! as AnyObject).isKind(of: NSNumber.self)
33 | {
34 | return nil
35 | }
36 | var result : String?
37 |
38 | #if os(iOS)
39 | if let stringRepresentation = (obj! as AnyObject).stringValue
40 | {
41 | if stringRepresentation.count > 0
42 | {
43 | var ordinal : String?
44 |
45 | if stringRepresentation == "11" || stringRepresentation == "12" || stringRepresentation == "13"
46 | {
47 | ordinal = "th"
48 | } else if let lastDigit = stringRepresentation.last
49 | {
50 | ordinal = OrdinalNumberFormatter.ordinalSuffixForLastDigit(lastDigit)
51 | }
52 |
53 | if let ordinal = ordinal
54 | {
55 | result = "\(stringRepresentation)\(ordinal)"
56 | }
57 | }
58 | }
59 | #elseif os(macOS)
60 | let stringRepresentation = (obj! as! NSNumber).stringValue
61 | if stringRepresentation.count > 0
62 | {
63 | var ordinal : String?
64 |
65 | if stringRepresentation == "11" || stringRepresentation == "12" || stringRepresentation == "13"
66 | {
67 | ordinal = "th"
68 | } else if let lastDigit = stringRepresentation.last
69 | {
70 | ordinal = OrdinalNumberFormatter.ordinalSuffixForLastDigit(lastDigit)
71 | }
72 |
73 | if let ordinal = ordinal
74 | {
75 | result = "\(stringRepresentation)\(ordinal)"
76 | }
77 | }
78 | #endif
79 |
80 | return result
81 | }
82 |
83 | open class func ordinalSuffixForLastDigit(_ digit : Character) -> String? {
84 | var result : String?
85 |
86 | if digit == "1"
87 | {
88 | result = "st"
89 | } else if digit == "2"
90 | {
91 | result = "nd"
92 | } else if digit == "3"
93 | {
94 | result = "rd"
95 | } else
96 | {
97 | result = "th"
98 | }
99 |
100 | return result
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Tests/String+UtilityTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+UtilityTests.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | import Eunomia
12 |
13 | class String_UtilityTests: XCTestCase {
14 |
15 | func testLength() {
16 | let test = "ASLKDFJLAKSJDFasdlfkjals;dkfjoaije"
17 |
18 | XCTAssertEqual(test.length, test.count)
19 | }
20 |
21 | func testAppend() {
22 | let zero = ""
23 | let notZero = "1234"
24 | let notZeroAppend = "ASDF"
25 | let join = "::"
26 |
27 | var selfZero = zero
28 | selfZero.append(notZeroAppend, withJoin: join)
29 | XCTAssertEqual(selfZero, notZeroAppend)
30 |
31 | var selfNotZero = notZero
32 | selfNotZero.append(notZeroAppend, withJoin: join)
33 | XCTAssertEqual(selfNotZero, notZero + join + notZeroAppend)
34 |
35 | selfNotZero = notZero
36 | selfNotZero.append(zero, withJoin: join)
37 | XCTAssertEqual(selfNotZero, notZero)
38 | }
39 |
40 | func testSubscriptCharacterResult() {
41 | let test = [Character("A"),Character("S"),Character("L"),Character("K"),Character("D"),Character("F"),Character("J"),Character("L"),Character("A"),Character("K"),Character("S"),Character("J"),Character("D"),Character("F"),Character("a"),Character("s"),Character("d"),Character("l"),Character("f"),Character("k"),Character("j"),Character("a"),Character("l"),Character("s"),Character(";"),Character("d"),Character("k"),Character("f"),Character("j"),Character("o"),Character("a"),Character("i"),Character("j"),Character("e")]
42 |
43 |
44 | let index = arc4random() % UInt32(test.count)
45 | let string = test.map({String($0)}).joined(separator: "")
46 |
47 | XCTAssertEqual(test[Int(index)], string[Int(index)])
48 | }
49 |
50 | func testSubscriptStringResult() {
51 | let test = ["A", "S", "L", "K", "D", "F", "J", "L","A","K","S","J","D","F","a","s","d","l","f","k","j","a","l","s",";","d","k","f","j","o","a","i","j","e"]
52 |
53 |
54 | let index = arc4random() % UInt32(test.count)
55 | let string = test.joined(separator: "")
56 |
57 | XCTAssertEqual(test[Int(index)], string[Int(index)])
58 | }
59 |
60 | func testSubstringIntRangeIndex() {
61 | let test = ["A", "S", "L", "K", "D", "F", "J", "L","A","K","S","J","D","F","a","s","d","l","f","k","j","a","l","s",";","d","k","f","j","o","a","i","j","e"]
62 |
63 |
64 | let index = 3..<10
65 | let testString = test[index].joined(separator: "")
66 | let string = test.joined(separator: "")
67 |
68 | XCTAssertEqual(testString, string[index])
69 | }
70 |
71 | // TODO: randomString()
72 |
73 | // func testData() {
74 | // let test = "ASDF@#$54235hsdfsgd;]345"
75 | //
76 | // XCTAssertEqual(test.data(using: <#String.Encoding#>), test.data(using: String.Encoding.utf8))
77 | // }
78 |
79 | // TODO: thorough valid characters and format testing
80 | func testIsValidEmailAddress() {
81 |
82 | var valid = "yo.ian.g@gmail.com"
83 | XCTAssertTrue(valid.isValidEmailAddress(), "\(valid) is a valid email address")
84 |
85 | valid = "asdf@asdf.io"
86 | XCTAssertTrue(valid.isValidEmailAddress(), "\(valid) is a valid email address")
87 |
88 | valid = "asdf@asdf.museum"
89 | XCTAssertTrue(valid.isValidEmailAddress(), "\(valid) is a valid email address")
90 |
91 | var invalid = "asdf@asdf"
92 | XCTAssertFalse(invalid.isValidEmailAddress(), "\(invalid) is an invalid email address")
93 |
94 | invalid = "asdf@asdf.i"
95 | XCTAssertFalse(invalid.isValidEmailAddress(), "\(invalid) is an invalid email address")
96 |
97 | invalid = "2345"
98 | XCTAssertFalse(invalid.isValidEmailAddress(), "\(invalid) is an invalid email address")
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Source/Core/Misc/ManagedObjectsObserver.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ManagedObjectsObserver.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 9/9/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | /*
10 | import Foundation
11 |
12 | import CoreData
13 |
14 | public class ManagedObjectsObserver : NSObject {
15 | private let observer : NSFetchedResultsController
16 | public var fetchedObjects : [NSManagedObject]? {
17 | return self.observer.fetchedObjects as? [NSManagedObject]
18 | }
19 |
20 | private var insertChanges = [NSIndexPath]()
21 | private var deleteChanges = [NSIndexPath]()
22 | // TODO: Moves and Updates
23 |
24 | public typealias OnObjectsDidChange = ( (controller: NSFetchedResultsController, insertChanges : [NSIndexPath], deleteChanges : [NSIndexPath]) -> Void)
25 | public var onObjectsDidChange : OnObjectsDidChange?
26 |
27 | public init(observer : NSFetchedResultsController, onObjectsDidChange : OnObjectsDidChange? = nil) {
28 | self.observer = observer
29 | self.onObjectsDidChange = onObjectsDidChange
30 |
31 | super.init()
32 |
33 | self.observer.delegate = self
34 | }
35 |
36 | deinit {
37 | self.observer.delegate = nil
38 | }
39 |
40 | public func refresh() throws {
41 | try self.observer.performFetch()
42 | }
43 |
44 | public var count : Int {
45 | return guarantee(self.fetchedObjects?.count, fallback: 0)
46 | }
47 |
48 | public func objectAtIndex(index : Int) -> NSManagedObject? {
49 | guard let fetchedObjects = fetchedObjects else {
50 | return nil
51 | }
52 |
53 | guard index < fetchedObjects.count else {
54 | return nil
55 | }
56 |
57 | return fetchedObjects[index]
58 | }
59 |
60 | public func objectAtIndex(index : Int) -> T? {
61 | guard let fetchedObjects = fetchedObjects else {
62 | return nil
63 | }
64 |
65 | guard index < fetchedObjects.count else {
66 | return nil
67 | }
68 |
69 | return fetchedObjects[index] as? T
70 | }
71 |
72 | // TODO: subscript
73 | }
74 |
75 | extension ManagedObjectsObserver : NSFetchedResultsControllerDelegate {
76 |
77 | public func controllerWillChangeContent(controller: NSFetchedResultsController) {
78 | self.insertChanges.removeAll()
79 | self.deleteChanges.removeAll()
80 | }
81 |
82 | public func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
83 |
84 | switch type {
85 | case .Insert:
86 | if let newIndexPath = newIndexPath {
87 | // TODO: should include object
88 | self.insertChanges.append(newIndexPath)
89 | } else {
90 | DDLog.error("Expected new index path with Insert")
91 | }
92 | break
93 |
94 | case .Delete:
95 | if let indexPath = indexPath {
96 | // TODO: should include object
97 | self.deleteChanges.append(indexPath)
98 | } else {
99 | DDLog.error("Expected index path with Delete")
100 | }
101 |
102 | default:
103 | break
104 | }
105 | }
106 |
107 | public func controllerDidChangeContent(controller: NSFetchedResultsController) {
108 |
109 | // guard self.insertChanges.count > 0 || self.deleteChanges.count > 0 else {
110 | //
111 | // DDLog.verbose("No values inserted or deleted, not notifying of changes")
112 | // return
113 | // }
114 |
115 | guard let onObjectsDidChange = self.onObjectsDidChange else {
116 | return
117 | }
118 |
119 | onObjectsDidChange(controller: controller, insertChanges: self.insertChanges, deleteChanges: self.deleteChanges)
120 | }
121 | }
122 | */
123 |
--------------------------------------------------------------------------------
/Eunomia.xcodeproj/xcshareddata/xcschemes/Eunomia-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
67 |
68 |
74 |
75 |
76 |
77 |
78 |
79 |
85 |
86 |
92 |
93 |
94 |
95 |
97 |
98 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/Eunomia.xcodeproj/xcshareddata/xcschemes/Eunomia-macOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
67 |
68 |
74 |
75 |
76 |
77 |
78 |
79 |
85 |
86 |
92 |
93 |
94 |
95 |
97 |
98 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/Source/Core/App/Logging.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logging.swift
3 | // Eunomia
4 | //
5 | // Created by Ian on 4/30/15.
6 | // Copyright (c) 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Log {
12 | enum Level: CustomStringConvertible {
13 | case error
14 | case warning
15 | case debug
16 | case info
17 | case verbose
18 |
19 | var description: String {
20 | let result: String
21 | switch self {
22 | case .error:
23 | result = "E"
24 |
25 | case .warning:
26 | result = "W"
27 |
28 | case .info:
29 | result = "I"
30 |
31 | case .debug:
32 | result = "D"
33 |
34 | case .verbose:
35 | result = "V"
36 | }
37 | return result
38 | }
39 | }
40 |
41 | struct Message {
42 | let message: String
43 | let level: Level
44 | let context: Int
45 | let file: String
46 | let function: String
47 | let line: UInt
48 |
49 | let threadName: String? = Thread.current.name
50 | }
51 |
52 | static var consoleLevel: Level = .info
53 | static let formatter = LogFormatter()
54 |
55 | public class func setupLogger(consoleLogLevel: Level = .info) throws {
56 | self.consoleLevel = consoleLogLevel
57 | }
58 |
59 | public class func log(message: String, level: Level, file: String, function: String, line: Int) {
60 | let message = Message(message: message, level: level, context: 0, file: file, function: function, line: UInt(line))
61 |
62 | let stringMessage = self.formatter.format(message)
63 | NSLog(stringMessage)
64 | }
65 |
66 | public class func error(message : String, fileName : String = #file, functionName : String = #function, line : Int = #line) {
67 | self.log(message: message, level: Level.error, file: fileName, function: functionName, line: line)
68 | }
69 |
70 | public class func warning(message : String, fileName : String = #file, functionName : String = #function, line : Int = #line) {
71 | self.log(message: message, level: Level.warning, file: fileName, function: functionName, line: line)
72 | }
73 |
74 | public class func info(message : String, fileName : String = #file, functionName : String = #function, line : Int = #line) {
75 | self.log(message: message, level: Level.info, file: fileName, function: functionName, line: line)
76 | }
77 |
78 | public class func debug(message : String, fileName : String = #file, functionName : String = #function, line : Int = #line) {
79 | self.log(message: message, level: Level.debug, file: fileName, function: functionName, line: line)
80 | }
81 |
82 | public class func verbose(message : String, fileName : String = #file, functionName : String = #function, line : Int = #line) {
83 | self.log(message: message, level: Level.verbose, file: fileName, function: functionName, line: line)
84 | }
85 |
86 | public class LogFormatter : NSObject {
87 | func format(_ message: Message) -> String {
88 |
89 | var result = String()
90 |
91 | result += message.level.description
92 |
93 | if let threadName = message.threadName,
94 | threadName.count > 0
95 | {
96 | result += " | thrd:\(threadName)"
97 | }
98 |
99 | result += ": \(message.message)"
100 |
101 | var fileFunction = String()
102 | if message.file.count > 0
103 | {
104 | fileFunction += "\((message.file as NSString).lastPathComponent):\(message.line):"
105 | }
106 | if message.function.count > 0
107 | {
108 | fileFunction += "\(String(describing: message.function))"
109 | }
110 | if fileFunction.count > 0
111 | {
112 | result += " | {\(fileFunction)}"
113 | }
114 |
115 | return result;
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/String+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian on 3/8/15.
6 | // Copyright (c) 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension String
12 | {
13 | var length : Int {
14 | return self.count
15 | }
16 |
17 | // TODO: deprecate in favor of joinWithSeparator
18 | mutating func append(_ string : String, withJoin : String = "") {
19 | if string.length > 0
20 | {
21 | if self.length > 0
22 | {
23 | self = self + withJoin + string
24 | } else
25 | {
26 | self = string
27 | }
28 | }
29 | }
30 |
31 | subscript (index : Int) -> Character {
32 | return self[ self.index(self.startIndex, offsetBy: index) ]
33 | }
34 |
35 | subscript (index: Int) -> String {
36 | return String(self[index] as Character)
37 | }
38 |
39 | subscript (range : Range) -> String {
40 | let indexRange = self.index(self.startIndex, offsetBy: range.lowerBound)..) -> String {
46 | let startIndex = self.index(self.startIndex, offsetBy: range.lowerBound)
47 | let endIndex = self.index(startIndex, offsetBy: range.upperBound - range.lowerBound)
48 | return String(self[startIndex...endIndex])
49 | }
50 | }
51 |
52 | extension String {
53 |
54 | // TODO: random string without repeat
55 | // TODO: NSCharacterSet
56 | public static func randomString(_ length : Int, allowedCharacters : String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") -> String {
57 | var result = String()
58 |
59 | for _ in 1...length
60 | {
61 | guard let randomCharacter : Character = allowedCharacters.randomElement() else {
62 | break
63 | }
64 | result.append(randomCharacter)
65 | }
66 |
67 | return result
68 | }
69 | }
70 |
71 | extension String {
72 | public func data() -> Data? {
73 | return self.data(using: String.Encoding.utf8)
74 | }
75 | }
76 |
77 | extension String {
78 |
79 | // Strict match based on http://blog.logichigh.com/2010/09/02/validating-an-e-mail-address/
80 | public func isValidEmailAddress() -> Bool {
81 | let emailRegex = "^[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$"
82 |
83 | let emailTest = NSPredicate(format: "SELF MATCHES %@", emailRegex)
84 |
85 | return emailTest.evaluate(with: self)
86 | }
87 | }
88 |
89 | extension String {
90 | public var stringByRemovingZeroDecimalValues : String {
91 | var previousStep = self
92 |
93 | var cleanZero = previousStep.replacingOccurrences(of: ".0", with: ".")
94 | while cleanZero != previousStep {
95 | previousStep = cleanZero
96 |
97 | cleanZero = previousStep.replacingOccurrences(of: ".0", with: ".")
98 | }
99 |
100 | let decimalRange = (previousStep as NSString).range(of: ".")
101 | if decimalRange.isValid() {
102 |
103 | if decimalRange.location == previousStep.count - 1 {
104 |
105 | previousStep = previousStep.replacingOccurrences(of: ".", with: "")
106 | } else {
107 | if previousStep.count - decimalRange.location < 2 {
108 | previousStep = previousStep + "0"
109 | }
110 | }
111 | }
112 |
113 | return previousStep
114 | }
115 | }
116 |
117 | // Based on: https://useyourloaf.com/blog/swift-hashable/
118 | extension String {
119 | public var djb2hash: Int {
120 | let unicodeScalars = self.unicodeScalars.map { $0.value }
121 | return unicodeScalars.reduce(5381) {
122 | ($0 << 5) &+ $0 &+ Int($1)
123 | }
124 | }
125 |
126 | public var sdbmhash: Int {
127 | let unicodeScalars = self.unicodeScalars.map { $0.value }
128 | return unicodeScalars.reduce(0) {
129 | Int($1) &+ ($0 << 6) &+ ($0 << 16) - $0
130 | }
131 | }
132 | }
133 |
134 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UITableView+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableView+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UITableView {
13 | public func heightForCell(_ reusableIdentifier : String, configure : (_ cell : UITableViewCell) -> Void) throws -> CGFloat {
14 |
15 | var result : CGFloat = 0
16 |
17 | if let cell = self.dequeueReusableCell(withIdentifier: reusableIdentifier)
18 | {
19 | cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: self.frame.size.width, height: CGFloat.greatestFiniteMagnitude)
20 |
21 | configure(cell)
22 |
23 | cell.setNeedsLayout()
24 | cell.contentView.setNeedsLayout()
25 | cell.layoutIfNeeded()
26 |
27 | cell.contentViewContraint(.height).forEach({ (constraint) -> () in
28 | result = constraint.constant
29 | })
30 |
31 | // result = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
32 |
33 | } else
34 | {
35 | throw NSError(description: "Unable to dequeue cell with reusable identifier \(reusableIdentifier)")
36 | }
37 |
38 | return result
39 |
40 | }
41 |
42 | public func refreshCellHeights() {
43 | self.beginUpdates()
44 | self.endUpdates()
45 | }
46 | }
47 |
48 | // MARK: - Static Table
49 | extension UITableView {
50 | /// TODO: once linker bug is fixed switch these back to Structs
51 | open class StaticSection
52 | {
53 | open class StaticHeader
54 | {
55 | open var title : String?
56 | public init(title : String?) {
57 | self.title = title
58 | }
59 | }
60 | let header : StaticHeader?
61 |
62 | open class StaticRow
63 | {
64 | public let cellPrototypeIdentifier : String
65 |
66 | public init(cellPrototypeIdentifier : String) {
67 | self.cellPrototypeIdentifier = cellPrototypeIdentifier
68 | }
69 | }
70 | open var rows = [StaticRow]()
71 |
72 | open func row(_ rowIndex : Int) -> StaticRow? {
73 |
74 | guard rowIndex < self.rows.count else {
75 | return nil
76 | }
77 |
78 | return self.rows[rowIndex]
79 | }
80 |
81 | public init(header : StaticHeader?) {
82 | self.header = header
83 | }
84 | }
85 |
86 | public func staticSection(_ sections : [StaticSection], sectionIndex : Int) -> UITableView.StaticSection? {
87 |
88 | guard sectionIndex < sections.count else {
89 | return nil
90 | }
91 |
92 | return sections[sectionIndex]
93 | }
94 |
95 | public func staticNumberOfRowsInSection(_ sections : [StaticSection], sectionIndex : Int) -> Int {
96 |
97 | guard let section = self.staticSection(sections, sectionIndex: sectionIndex) else {
98 | return 0
99 | }
100 |
101 | return section.rows.count
102 | }
103 |
104 | public func staticTitleForHeaderInSection(_ sections : [StaticSection], sectionIndex : Int) -> String? {
105 | guard let section = self.staticSection(sections, sectionIndex: sectionIndex) else {
106 | return nil
107 | }
108 |
109 | guard let header = section.header else {
110 | return nil
111 | }
112 |
113 | return header.title
114 | }
115 |
116 | public func staticCellForRowAtIndexPath(_ sections : [StaticSection], indexPath : IndexPath, fallbackIdentifier : String) -> UITableViewCell {
117 |
118 | guard let section = self.staticSection(sections, sectionIndex: indexPath.section) else {
119 | return self.dequeueReusableCell(withIdentifier: fallbackIdentifier, for: indexPath)
120 | }
121 |
122 | guard let row = section.row(indexPath.row) else {
123 | return self.dequeueReusableCell(withIdentifier: fallbackIdentifier, for: indexPath)
124 | }
125 |
126 | return self.dequeueReusableCell(withIdentifier: row.cellPrototypeIdentifier, for: indexPath)
127 | }
128 | }
129 | #endif
130 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UIAlertController+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIAlertController+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UIAlertController
13 | {
14 | public class func showAlert(
15 | _ title : String? = nil,
16 | message : String? = nil,
17 | buttonText : String = "Ok",
18 | showOver : UIViewController,
19 | completion completionHandler : (() -> Void)? = nil
20 | ) {
21 |
22 | let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
23 |
24 | alert.addAction(UIAlertAction(title: buttonText, style: UIAlertAction.Style.default, handler: { (action) -> Void in
25 | alert.dismiss(animated: true, completion: completionHandler)
26 | }))
27 |
28 | showOver.present(alert, animated: true, completion: nil)
29 | }
30 |
31 | public class func showEmailReportAlert(
32 | _ title : String? = nil,
33 | message : String? = nil,
34 | defaultButtonText : String = "Ok",
35 | reportButtonText : String = "Report",
36 | reportEmailAddress : String,
37 | reportSubject : String,
38 | reportBody : String? = nil,
39 | showOver : UIViewController,
40 | completion completionHandler : ( (_ error : NSError?) -> Void)? = nil
41 | ) {
42 |
43 | let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
44 |
45 | alert.addAction(UIAlertAction(title: defaultButtonText, style: UIAlertAction.Style.default, handler: { (action) -> Void in
46 | alert.dismiss(animated: true, completion: { () -> Void in
47 |
48 | if let completionHandler = completionHandler {
49 | completionHandler(nil)
50 | }
51 | })
52 | }))
53 |
54 | alert.addAction(UIAlertAction(title: reportButtonText, style: UIAlertAction.Style.default, handler: { (action) -> Void in
55 | alert.dismiss(animated: true, completion: { () -> Void in
56 |
57 | let error : NSError?
58 | do {
59 | let application = try AppExtensionSafeApplication.sharedThrowing()
60 | try application.mailTo(reportEmailAddress, subject: reportSubject, body: reportBody)
61 |
62 | error = nil
63 | } catch let mailToError as NSError {
64 |
65 | error = mailToError
66 | }
67 |
68 | if let completionHandler = completionHandler {
69 | completionHandler(error)
70 | }
71 | })
72 | }))
73 |
74 | showOver.present(alert, animated: true, completion: nil)
75 | }
76 |
77 | public class func deleteActionSheet(
78 | _ title : String,
79 | deleteActionText : String = "Delete",
80 | cancelActionText : String = "Cancel",
81 | autoDismiss : Bool = true,
82 | deleteActionHandler : @escaping ((UIAlertAction) -> Void),
83 | cancelActionHandler : ((UIAlertAction) -> Void)?
84 | ) -> UIAlertController {
85 |
86 | let result = UIAlertController(title: title, message: nil, preferredStyle: UIAlertController.Style.actionSheet)
87 |
88 | let deleteAction = UIAlertAction(title: deleteActionText, style: UIAlertAction.Style.destructive, handler: { (action) -> Void in
89 | if autoDismiss
90 | {
91 | result.dismiss(animated: true, completion: nil)
92 | }
93 |
94 | deleteActionHandler(action)
95 | })
96 | result.addAction(deleteAction)
97 |
98 | let cancelAction = UIAlertAction(title: cancelActionText, style: UIAlertAction.Style.cancel, handler: { (action) -> Void in
99 | if autoDismiss
100 | {
101 | result.dismiss(animated: true, completion: nil)
102 | }
103 |
104 | if let cancelActionHandler = cancelActionHandler
105 | {
106 | cancelActionHandler(action)
107 | }
108 | })
109 | result.addAction(cancelAction)
110 |
111 | return result
112 | }
113 | }
114 | #endif
115 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/PHPhotoLibrary+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PHPhotoLibrary+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 12/1/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Photos
10 | import UIKit
11 |
12 | @available(OSX 10.13, iOS 10.3, *)
13 | extension PHPhotoLibrary {
14 |
15 | #if os(iOS)
16 | public enum RetrieveFirstImageResult {
17 | case success(UIImage)
18 | case noImages
19 | case denied(PHAuthorizationStatus)
20 | case error(NSError)
21 | }
22 | public func retrieveFirstImage(requestAuthorizationIfNeeded : Bool = true, completion completionHandler : @escaping (RetrieveFirstImageResult) -> Void) {
23 |
24 | let retrieve = { () -> Void in
25 | let collections = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil)
26 |
27 | guard collections.count > 0 else {
28 | completionHandler(.noImages)
29 | return
30 | }
31 |
32 | guard let collection = collections.firstObject else {
33 |
34 | let error = NSError(description: "Expected to be given a \(PHAssetCollection.self), instead given a \(type(of: collections.firstObject))")
35 | completionHandler(.error(error))
36 | return
37 | }
38 |
39 | // TODO: test that our collection is Camera Roll
40 | let cameraRoll = collection
41 |
42 | let options = PHFetchOptions()
43 | options.predicate = NSPredicate(format: "mediaType == %d", PHAssetMediaType.image.rawValue)
44 | options.includeAssetSourceTypes = PHAssetSourceType.typeUserLibrary
45 | options.fetchLimit = 1
46 | options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
47 |
48 | let assets = PHAsset.fetchAssets(in: cameraRoll, options: options)
49 | guard assets.count > 0 else {
50 | completionHandler(.noImages)
51 | return
52 | }
53 |
54 | guard let asset = assets.firstObject else {
55 |
56 | let error = NSError(description: "Expected to be given a \(PHAsset.self), instead given a \(type(of: assets.firstObject))")
57 | completionHandler(.error(error))
58 | return
59 | }
60 |
61 | let imageManager : PHImageManager = PHImageManager()
62 | imageManager.requestImageData(for: asset, options: nil, resultHandler: { (data, dataUTI, orientation, info) -> Void in
63 |
64 | guard let data = data else {
65 | completionHandler(.noImages)
66 | return
67 | }
68 |
69 | guard let photo = UIImage(data: data) else {
70 |
71 | let error = NSError(description: "Unable to create UIImage from data")
72 | completionHandler(.error(error))
73 | return
74 | }
75 |
76 | completionHandler(.success(photo))
77 | })
78 | }
79 |
80 | switch PHPhotoLibrary.authorizationStatus() {
81 | case .authorized:
82 | retrieve()
83 | break
84 |
85 | case .notDetermined:
86 | if requestAuthorizationIfNeeded == true {
87 | PHPhotoLibrary.requestAuthorization(self.requestAuthorizationToRetrieveFirstImageHandler(completion: completionHandler))
88 | } else {
89 | completionHandler(.denied(.notDetermined))
90 | }
91 | break
92 |
93 | case .restricted:
94 | completionHandler(.denied(.restricted))
95 | break
96 |
97 | case .denied:
98 | completionHandler(.denied(.denied))
99 | break
100 | @unknown default:
101 | fatalError("Unexpected PHAuthorizationStatus")
102 | }
103 |
104 | }
105 |
106 | fileprivate func requestAuthorizationToRetrieveFirstImageHandler(completion completionHandler : @escaping (RetrieveFirstImageResult) -> Void) -> ( (PHAuthorizationStatus) -> Void) {
107 |
108 | return { (result) -> Void in
109 |
110 | // We're checking authorization status in retrieveFirstImage already, let's reuse our code but not re-request authorization
111 | self.retrieveFirstImage(requestAuthorizationIfNeeded: false, completion: completionHandler)
112 | }
113 | }
114 | #endif
115 | }
116 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/CoreData/NSManagedObject+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSManagedObject+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | import CoreData
12 |
13 | // MARK: - Creation
14 | extension NSManagedObject {
15 |
16 | public class var entityName : String { // Assumes we've named our CoreData object the same as the Class object
17 |
18 | let fullClassName: String = NSStringFromClass(object_getClass(self)!)
19 | let classNameComponents: [String] = fullClassName.split { $0 == "." }.map { String($0) }
20 | return classNameComponents.last!
21 | }
22 |
23 | public class func entityDescription(_ inContext: NSManagedObjectContext) -> NSEntityDescription? {
24 | return NSEntityDescription.entity(forEntityName: self.entityName, in: inContext)
25 | }
26 |
27 | public class func insertNewObjectInContext(_ context: NSManagedObjectContext) -> NSManagedObject {
28 | return NSEntityDescription.insertNewObject(forEntityName: self.entityName, into: context)
29 | }
30 | }
31 |
32 | // MARK: - Sort Descriptors
33 | extension NSManagedObject {
34 |
35 | public class func defaultSortDescriptors() -> [NSSortDescriptor] {
36 | return [NSSortDescriptor]()
37 | }
38 |
39 | public class func guaranteeSortDescriptors(_ potentialSortDescriptors : [NSSortDescriptor]?) -> [NSSortDescriptor] {
40 |
41 | return guarantee(potentialSortDescriptors, fallback: self.defaultSortDescriptors())
42 | }
43 |
44 | }
45 |
46 | // MARK: - Fetch Request
47 | extension NSManagedObject {
48 |
49 | public class func defaultFetchRequest(_ sortDescriptors : [NSSortDescriptor]? = nil) -> NSFetchRequest {
50 |
51 | let result = NSFetchRequest(entityName: self.entityName)
52 | result.sortDescriptors = guarantee(sortDescriptors, fallback: self.defaultSortDescriptors())
53 | return result
54 | }
55 |
56 | public class func guaranteeFetchRequest(_ potentialFetchRequest : NSFetchRequest?) -> NSFetchRequest {
57 | return guarantee(potentialFetchRequest, fallback: self.defaultFetchRequest())
58 | }
59 |
60 | }
61 |
62 | // MARK: - Fetched Results Controller
63 | extension NSManagedObject {
64 |
65 | public class func defaultFetchedResultsController(_ fetchRequest : NSFetchRequest? = nil, predicate : NSPredicate? = nil, sortDescriptors : [NSSortDescriptor]? = nil, inContext context: NSManagedObjectContext) -> NSFetchedResultsController {
66 |
67 | let useFetchRequest = guarantee(fetchRequest, fallback: self.defaultFetchRequest())
68 |
69 | if let predicate = predicate {
70 | useFetchRequest.predicate = predicate
71 | }
72 |
73 | if let sortDescriptors = sortDescriptors {
74 | useFetchRequest.sortDescriptors = sortDescriptors
75 | }
76 |
77 | return NSFetchedResultsController(fetchRequest: useFetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
78 | }
79 |
80 | }
81 |
82 | // MARK: - Save
83 | extension NSManagedObject {
84 |
85 | public func saveOrLogError(_ logContext : String) -> Bool {
86 | var result : Bool
87 |
88 | if let context = self.managedObjectContext
89 | {
90 | result = context.saveOrLogError(logContext)
91 | } else
92 | {
93 | // TODO: throw
94 | result = false
95 | }
96 |
97 | return result
98 | }
99 | }
100 |
101 | // MARK: - Delete
102 | extension NSManagedObject {
103 |
104 | public func deleteObject() -> Bool {
105 | var result : Bool
106 |
107 | if let context = self.managedObjectContext
108 | {
109 | context.delete(self)
110 | result = true
111 | } else
112 | {
113 | // TODO: throw
114 | result = false
115 | }
116 |
117 | return result
118 | }
119 |
120 | public func deleteAndSaveOrLogError(_ logContext : String) -> Bool {
121 | var result : Bool
122 |
123 | if let context = self.managedObjectContext
124 | {
125 | // TODO: let user know if delete or save failed
126 | result = self.deleteObject()
127 | if result == true
128 | {
129 | result = context.saveOrLogError(logContext)
130 | }
131 | } else
132 | {
133 | // TODO: throw
134 | result = false
135 | }
136 |
137 | return result
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UITabBarController+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITabBarController+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 11/26/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UITabBarController {
13 |
14 | public func tabIndexOf(_ match : UIViewController) -> Int? {
15 | var result : Int?
16 |
17 | if let viewControllers = self.viewControllers
18 | {
19 | for index in 0...viewControllers.count - 1
20 | {
21 | let viewControllerAtIndex = viewControllers[index]
22 |
23 | if viewControllerAtIndex == match
24 | {
25 | result = index
26 | break
27 | } else if viewControllerAtIndex.isKind(of: UINavigationController.self)
28 | {
29 | let navigationController = viewControllerAtIndex as! UINavigationController
30 | if navigationController.viewControllers.count > 0
31 | {
32 | let viewControllerAtNavRoot = navigationController.viewControllers[0]
33 | if viewControllerAtNavRoot == match
34 | {
35 | result = index
36 | break
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
43 | if result == nil
44 | {
45 | Log.info(message: "View controller \(match) was not found in any tab")
46 | }
47 |
48 | return result
49 | }
50 |
51 | public func tabIndexForTabWithClass(_ matchClass : UIViewController.Type) -> Int? {
52 | var result : Int?
53 |
54 | if let viewControllers = self.viewControllers
55 | {
56 | for index in 0...viewControllers.count - 1
57 | {
58 | if viewControllers[index].isKind(of: matchClass)
59 | {
60 | result = index
61 | break
62 | } else if viewControllers[index].isKind(of: UINavigationController.self)
63 | {
64 | let navigationController = viewControllers[index] as! UINavigationController
65 | if navigationController.viewControllers.count > 0 &&
66 | navigationController.viewControllers[0].isKind(of: matchClass)
67 | {
68 | result = index
69 | break
70 | }
71 | }
72 | }
73 | }
74 |
75 | if result == nil
76 | {
77 | Log.info(message: "No view controller class found of type \(matchClass)")
78 | }
79 |
80 | return result
81 | }
82 |
83 | public func selectTabWithClass(_ matchClass : UIViewController.Type) {
84 | if let index = self.tabIndexForTabWithClass(matchClass)
85 | {
86 | self.selectedIndex = index
87 | }
88 | }
89 |
90 | public func preloadTabs() {
91 |
92 | if let viewControllers = self.viewControllers {
93 |
94 | for viewController in viewControllers {
95 | let _ = viewController.view // Oh how I hate View Controllers' "view access side effect"
96 | }
97 | }
98 | }
99 |
100 | public func preloadTab(_ matchClass : UIViewController.Type) throws {
101 | guard let viewControllers = self.viewControllers else {
102 | return
103 | }
104 |
105 | guard let index = self.tabIndexForTabWithClass(matchClass) else {
106 | throw NSError(description: "No tab found with class \(matchClass)")
107 | }
108 |
109 | guard index < viewControllers.count else {
110 | // TODO: something is broken, log but don't throw
111 | return
112 | }
113 |
114 | let _ = viewControllers[index].view
115 | }
116 |
117 | public func tabBarButtonWithClass(_ matchClass : UIViewController.Type) throws -> UIView? {
118 | guard let tabBarButtons = try self.tabBar.tabBarButtonViews() else {
119 | return nil
120 | }
121 |
122 | guard let index = self.tabIndexForTabWithClass(matchClass) else {
123 | return nil
124 | }
125 |
126 | guard index < tabBarButtons.count else {
127 | // This is totally normal if the tab bar has a "More" button
128 | return nil
129 | }
130 |
131 | return tabBarButtons[index]
132 | }
133 |
134 | public func rectForTabWithClass(_ matchClass : UIViewController.Type) throws -> CGRect? {
135 |
136 | guard let tabBarButton = try self.tabBarButtonWithClass(matchClass) else {
137 | return nil
138 | }
139 |
140 | return tabBarButton.frame
141 | }
142 | }
143 | #endif
144 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/CLLocation+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CLLocation+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 8/24/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreLocation
11 |
12 | extension CLLocation {
13 | public convenience init(latitude: Measurement, longitude: Measurement) {
14 | self.init(latitude: CLLocationDegrees(from: latitude), longitude: CLLocationDegrees(from: longitude))
15 | }
16 |
17 | public convenience init(coordinate: CLLocationCoordinate2D) {
18 | self.init(latitude: coordinate.latitude, longitude: coordinate.longitude)
19 | }
20 | }
21 |
22 | extension CLLocation {
23 | public func closest(from: [CLLocation]) -> CLLocation? {
24 | var result: CLLocation? = nil
25 | var closestDistance: CLLocationDistance = CLLocationDistance.infinity
26 |
27 | for testLocation in from {
28 | let testDistance = self.distance(from: testLocation)
29 | if result == nil || testDistance < closestDistance {
30 | result = testLocation
31 | closestDistance = testDistance
32 | }
33 | }
34 |
35 | return result
36 | }
37 | }
38 |
39 | extension CLLocation {
40 | public func heading(to: CLLocation) -> Measurement {
41 | return self.coordinate.heading(to: to.coordinate)
42 | }
43 |
44 | public func heading(to: CLLocation) -> CLLocationDirection {
45 | return self.coordinate.heading(to: to.coordinate)
46 | }
47 | }
48 |
49 | extension CLLocation: Encodable {
50 | public enum CodingKeys: String, CodingKey {
51 | case latitude
52 | case longitude
53 | case altitude
54 | case horizontalAccuracy
55 | case verticalAccuracy
56 | case speed
57 | case course
58 | case timestamp
59 | }
60 |
61 | public func encode(to encoder: Encoder) throws {
62 | var container = encoder.container(keyedBy: CodingKeys.self)
63 |
64 | try container.encode(coordinate.latitude, forKey: .latitude)
65 | try container.encode(coordinate.longitude, forKey: .longitude)
66 | try container.encode(altitude, forKey: .altitude)
67 | try container.encode(horizontalAccuracy, forKey: .horizontalAccuracy)
68 | try container.encode(verticalAccuracy, forKey: .verticalAccuracy)
69 | try container.encode(speed, forKey: .speed)
70 | try container.encode(course, forKey: .course)
71 | try container.encode(timestamp, forKey: .timestamp)
72 | }
73 | }
74 |
75 | // Based on https://medium.com/@kf99916/codable-nsmanagedobject-and-cllocation-in-swift-4-b32f042cb7d3
76 | public struct CodableLocation: Codable {
77 | let latitude: CLLocationDegrees
78 | let longitude: CLLocationDegrees
79 | let altitude: CLLocationDistance
80 | let horizontalAccuracy: CLLocationAccuracy
81 | let verticalAccuracy: CLLocationAccuracy
82 | let speed: CLLocationSpeed
83 | let course: CLLocationDirection
84 | let timestamp: Date
85 |
86 | var coordinate: CLLocationCoordinate2D {
87 | return CLLocationCoordinate2DMake(self.latitude, self.longitude)
88 | }
89 |
90 | init(latitude: CLLocationDegrees,
91 | longitude: CLLocationDegrees,
92 | altitude: CLLocationDistance,
93 | horizontalAccuracy: CLLocationAccuracy,
94 | verticalAccuracy: CLLocationAccuracy,
95 | speed: CLLocationSpeed,
96 | course: CLLocationDirection,
97 | timestamp: Date) {
98 | self.latitude = latitude
99 | self.longitude = longitude
100 | self.altitude = altitude
101 | self.horizontalAccuracy = horizontalAccuracy
102 | self.verticalAccuracy = verticalAccuracy
103 | self.speed = speed
104 | self.course = course
105 | self.timestamp = timestamp
106 | }
107 |
108 | public init(from: CLLocation) {
109 | self.init(latitude: from.coordinate.latitude, longitude: from.coordinate.longitude, altitude: from.altitude, horizontalAccuracy: from.horizontalAccuracy, verticalAccuracy: from.verticalAccuracy, speed: from.speed, course: from.course, timestamp: from.timestamp)
110 | }
111 | }
112 |
113 | extension CLLocation {
114 | public convenience init(model: CodableLocation) {
115 | self.init(coordinate: model.coordinate, altitude: model.altitude, horizontalAccuracy: model.horizontalAccuracy, verticalAccuracy: model.verticalAccuracy, course: model.course, speed: model.speed, timestamp: model.timestamp)
116 | }
117 | }
118 |
119 | //
120 | //extension CLLocation: Decodable {
121 | // enum CodingKeys: String, CodingKey {
122 | // case latitude = "latitude"
123 | // case longitude = "longitude"
124 | // }
125 | // public convenience init(from decoder: Decoder) throws {
126 | // let container = try decoder.container(keyedBy: CLLocation.CodingKeys.self)
127 | // let latitude = try container.decode(CLLocationDegrees.self, forKey: .latitude)
128 | // let longitude = try container.decode(CLLocationDegrees.self, forKey: .longitude)
129 | //
130 | // self.init(latitude: latitude, longitude: longitude)
131 | // }
132 | //}
133 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/NSColor+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSColor+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 10/5/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(macOS)
10 | import Cocoa
11 |
12 | extension NSColor {
13 |
14 | public class func randomComponent() -> CGFloat {
15 | return CGFloat(Float.random(in: 0.0...1.0))
16 | }
17 |
18 | public class func randomColor(alpha : CGFloat = 1.0) -> NSColor {
19 | return NSColor(red: self.randomComponent(), green: self.randomComponent(), blue: self.randomComponent(), alpha: alpha)
20 | }
21 |
22 | public class func randomColorWithRandomAlpha() -> NSColor {
23 | return self.randomColor(alpha: self.randomComponent())
24 | }
25 |
26 | fileprivate class func increase(_ components : inout [CGFloat], byAmount amount : CGFloat) {
27 |
28 | var increased = [CGFloat]()
29 |
30 | for component in components {
31 |
32 | let increaseTo = min(component + amount, 1.0)
33 | increased.append(increaseTo)
34 | }
35 |
36 | components = increased
37 | }
38 |
39 | public class func randomColor(minimumIntensity : CGFloat) -> NSColor {
40 |
41 | var components = [
42 | self.randomComponent(),
43 | self.randomComponent(),
44 | self.randomComponent()
45 | ]
46 |
47 | var biggestDifference : CGFloat = 0
48 | for component in components {
49 | if component < minimumIntensity {
50 | let difference = minimumIntensity - component
51 |
52 | if difference > biggestDifference {
53 | biggestDifference = difference
54 | }
55 | }
56 | }
57 |
58 | if biggestDifference > 0 {
59 | self.increase(&components, byAmount: biggestDifference)
60 | }
61 |
62 | return self.colorWithRGBComponents(components)
63 | }
64 |
65 | fileprivate class func componentValueAtIndex(_ components : [CGFloat], index : Int) -> CGFloat? {
66 | guard index < components.count else {
67 | return nil
68 | }
69 |
70 | return components[index]
71 | }
72 |
73 | public class func colorWithRGBComponents(_ components : [CGFloat]) -> NSColor {
74 | let red = guarantee(self.componentValueAtIndex(components, index: 0), fallback: 0)
75 | let green = guarantee(self.componentValueAtIndex(components, index: 1), fallback: 0)
76 | let blue = guarantee(self.componentValueAtIndex(components, index: 2), fallback: 0)
77 | let alpha = guarantee(self.componentValueAtIndex(components, index: 3), fallback: 1)
78 |
79 | return NSColor(red: red, green: green, blue: blue, alpha: alpha)
80 | }
81 |
82 | public class func byteComponentToCGFloat(_ value : u_char) -> CGFloat {
83 | return CGFloat(value) / 255.0
84 | }
85 | }
86 |
87 | extension NSColor {
88 | @available(OSX 10.13, *)
89 | public static func createFrom(named: String, fallback: NSColor) -> NSColor {
90 | guard let named = NSColor(named: named) else {
91 | return fallback
92 | }
93 | return named
94 | }
95 | }
96 |
97 | // Based on: https://stackoverflow.com/questions/24263007/how-to-use-hex-colour-values
98 | extension NSColor {
99 | public convenience init(red: Int, green: Int, blue: Int) {
100 | self.init(
101 | red: red,
102 | green: green,
103 | blue: blue,
104 | alpha: 1.0)
105 | }
106 |
107 | // Support alpha
108 | public convenience init(rgb: Int) {
109 | self.init(
110 | red: (rgb >> 16) & 0xFF,
111 | green: (rgb >> 8) & 0xFF,
112 | blue: rgb & 0xFF
113 | )
114 | }
115 |
116 | public convenience init(red: Int, green: Int, blue: Int, alpha: CGFloat = 1.0) {
117 | // assert(red >= 0 && red <= 255, "Invalid red component")
118 | // assert(green >= 0 && green <= 255, "Invalid green component")
119 | // assert(blue >= 0 && blue <= 255, "Invalid blue component")
120 |
121 | self.init(
122 | red: CGFloat(red) / 255.0,
123 | green: CGFloat(green) / 255.0,
124 | blue: CGFloat(blue) / 255.0,
125 | alpha: alpha
126 | )
127 | }
128 |
129 | public class InvalidFormatError: Error {
130 | }
131 | public convenience init(hex: String) throws {
132 | var string: String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
133 |
134 | if string.hasPrefix("#") {
135 | string.remove(at: string.startIndex)
136 | }
137 |
138 | // Support with alpha
139 | if string.count != 6 {
140 | throw InvalidFormatError()
141 | }
142 |
143 | var rgbValue:UInt32 = 0
144 | Scanner(string: string).scanHexInt32(&rgbValue)
145 |
146 | self.init(
147 | red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
148 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
149 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
150 | alpha: CGFloat(1.0)
151 | )
152 | }
153 | }
154 | #endif
155 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/UIColor+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 12/1/15.
6 | // Copyright © 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | extension UIColor {
13 |
14 | public class func randomComponent() -> CGFloat {
15 | return CGFloat(CGFloat.random(in: 0.0...1.0))
16 | }
17 |
18 | public class func randomColor(alpha : CGFloat = 1.0) -> UIColor {
19 | return UIColor(red: self.randomComponent(), green: self.randomComponent(), blue: self.randomComponent(), alpha: alpha)
20 | }
21 |
22 | public class func randomColorWithRandomAlpha() -> UIColor {
23 | return self.randomColor(alpha: self.randomComponent())
24 | }
25 |
26 | fileprivate class func increase(_ components : inout [CGFloat], byAmount amount : CGFloat) {
27 |
28 | var increased = [CGFloat]()
29 |
30 | for component in components {
31 |
32 | let increaseTo = min(component + amount, 1.0)
33 | increased.append(increaseTo)
34 | }
35 |
36 | components = increased
37 | }
38 |
39 | public class func randomColor(minimumIntensity : CGFloat) -> UIColor {
40 |
41 | var components = [
42 | self.randomComponent(),
43 | self.randomComponent(),
44 | self.randomComponent()
45 | ]
46 |
47 | var biggestDifference : CGFloat = 0
48 | for component in components {
49 | if component < minimumIntensity {
50 | let difference = minimumIntensity - component
51 |
52 | if difference > biggestDifference {
53 | biggestDifference = difference
54 | }
55 | }
56 | }
57 |
58 | if biggestDifference > 0 {
59 | self.increase(&components, byAmount: biggestDifference)
60 | }
61 |
62 | return self.colorWithRGBComponents(components)
63 | }
64 |
65 | fileprivate class func componentValueAtIndex(_ components : [CGFloat], index : Int) -> CGFloat? {
66 | guard index < components.count else {
67 | return nil
68 | }
69 |
70 | return components[index]
71 | }
72 |
73 | public class func colorWithRGBComponents(_ components : [CGFloat]) -> UIColor {
74 | let red = guarantee(self.componentValueAtIndex(components, index: 0), fallback: 0)
75 | let green = guarantee(self.componentValueAtIndex(components, index: 1), fallback: 0)
76 | let blue = guarantee(self.componentValueAtIndex(components, index: 2), fallback: 0)
77 | let alpha = guarantee(self.componentValueAtIndex(components, index: 3), fallback: 1)
78 |
79 | return UIColor(red: red, green: green, blue: blue, alpha: alpha)
80 | }
81 |
82 | public class func byteComponentToCGFloat(_ value : u_char) -> CGFloat {
83 | return CGFloat(value) / 255.0
84 | }
85 | }
86 |
87 | extension UIColor {
88 | public static func createFrom(named: String, fallback: UIColor) -> UIColor {
89 | guard #available(iOS 11.0, *) else {
90 | return fallback
91 | }
92 | guard let named = UIColor(named: named) else {
93 | return fallback
94 | }
95 | return named
96 | }
97 | }
98 |
99 | // Based on: https://stackoverflow.com/questions/24263007/how-to-use-hex-colour-values
100 | extension UIColor {
101 | public convenience init(red: Int, green: Int, blue: Int) {
102 | self.init(
103 | red: red,
104 | green: green,
105 | blue: blue,
106 | alpha: 1.0)
107 | }
108 |
109 | // Support alpha
110 | public convenience init(rgb: Int) {
111 | self.init(
112 | red: (rgb >> 16) & 0xFF,
113 | green: (rgb >> 8) & 0xFF,
114 | blue: rgb & 0xFF
115 | )
116 | }
117 |
118 | public convenience init(red: Int, green: Int, blue: Int, alpha: CGFloat = 1.0) {
119 | // assert(red >= 0 && red <= 255, "Invalid red component")
120 | // assert(green >= 0 && green <= 255, "Invalid green component")
121 | // assert(blue >= 0 && blue <= 255, "Invalid blue component")
122 |
123 | self.init(
124 | red: CGFloat(red) / 255.0,
125 | green: CGFloat(green) / 255.0,
126 | blue: CGFloat(blue) / 255.0,
127 | alpha: alpha
128 | )
129 | }
130 |
131 | public class InvalidFormatError: Error {
132 | }
133 | public convenience init(hex: String) throws {
134 | var string: String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
135 |
136 | if string.hasPrefix("#") {
137 | string.remove(at: string.startIndex)
138 | }
139 |
140 | // Support with alpha
141 | if string.count != 6 {
142 | throw InvalidFormatError()
143 | }
144 |
145 | var rgbValue:UInt32 = 0
146 | Scanner(string: string).scanHexInt32(&rgbValue)
147 |
148 | self.init(
149 | red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
150 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
151 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
152 | alpha: CGFloat(1.0)
153 | )
154 | }
155 | }
156 | #endif
157 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/Views/UIImageView+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageView+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian on 5/6/15.
6 | // Copyright (c) 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 |
12 | public extension UIImageView {
13 |
14 | // http://stackoverflow.com/a/6857098
15 | func imageDisplayScale() -> CGSize {
16 | var result : CGSize
17 |
18 | if let image = self.image
19 | {
20 | let sx = Float(self.frame.size.width / image.size.width)
21 | let sy = Float(self.frame.size.height / image.size.height)
22 | var s : CGFloat = 1.0
23 |
24 | switch self.contentMode
25 | {
26 | case UIView.ContentMode.scaleAspectFit:
27 | s = CGFloat( fminf(sx, sy) )
28 | result = CGSize(width: s, height: s)
29 | break
30 |
31 | case UIView.ContentMode.scaleAspectFill:
32 | s = CGFloat( fmaxf(sx, sy) )
33 | result = CGSize(width: s, height: s)
34 | break
35 |
36 | case UIView.ContentMode.scaleToFill:
37 | result = CGSize( width: CGFloat(sx), height: CGFloat(sy) )
38 | break
39 |
40 | default:
41 | result = CGSize(width: s, height: s)
42 | }
43 | } else
44 | {
45 | result = CGSize.zero
46 | }
47 |
48 | return result
49 | }
50 |
51 | func imageDisplaySize() -> CGSize {
52 | var result : CGSize
53 |
54 | if self.image != nil
55 | {
56 | let imageScale = self.imageDisplayScale()
57 |
58 | result = CGSize(width: self.image!.size.width * imageScale.width, height: self.image!.size.height * imageScale.height)
59 | } else
60 | {
61 | result = CGSize.zero
62 | }
63 |
64 | return result
65 | }
66 |
67 | func imageDisplayFrame() -> CGRect {
68 | var result : CGRect
69 |
70 | if self.image != nil
71 | {
72 | switch self.contentMode
73 | {
74 | case UIViewContentMode.scaleAspectFit:
75 | let displaySize = self.imageDisplaySize()
76 | result = CGRect(x: (self.bounds.width - displaySize.width) / 2, y: (self.bounds.height - displaySize.height) / 2, width: displaySize.width, height: displaySize.height)
77 | break
78 |
79 | case UIViewContentMode.scaleAspectFill:
80 | let displaySize = self.imageDisplaySize()
81 | result = CGRect(x: (self.bounds.width - displaySize.width) / 2, y: (self.bounds.height - displaySize.height) / 2, width: displaySize.width, height: displaySize.height)
82 | break
83 |
84 | case UIViewContentMode.scaleToFill:
85 | result = self.bounds
86 | break
87 |
88 | case UIViewContentMode.top:
89 | NSLog("TODO: Eunomia UIImageView+Utility, imageDisplayFrame(): implement handling ContentMode")
90 | result = CGRect.zero
91 | break
92 |
93 | case UIViewContentMode.topLeft:
94 | NSLog("TODO: Eunomia UIImageView+Utility, imageDisplayFrame(): implement handling ContentMode")
95 | result = CGRect.zero
96 | break
97 |
98 | case UIViewContentMode.topRight:
99 | NSLog("TODO: Eunomia UIImageView+Utility, imageDisplayFrame(): implement handling ContentMode")
100 | result = CGRect.zero
101 | break
102 |
103 | case UIViewContentMode.bottom:
104 | NSLog("TODO: Eunomia UIImageView+Utility, imageDisplayFrame(): implement handling ContentMode")
105 | result = CGRect.zero
106 | break
107 |
108 | case UIViewContentMode.bottomLeft:
109 | NSLog("TODO: Eunomia UIImageView+Utility, imageDisplayFrame(): implement handling ContentMode")
110 | result = CGRect.zero
111 | break
112 |
113 | case UIViewContentMode.bottomRight:
114 | NSLog("TODO: Eunomia UIImageView+Utility, imageDisplayFrame(): implement handling ContentMode")
115 | result = CGRect.zero
116 | break
117 |
118 | case UIViewContentMode.center:
119 | NSLog("TODO: Eunomia UIImageView+Utility, imageDisplayFrame(): implement handling ContentMode")
120 | result = CGRect.zero
121 | break
122 |
123 | case UIViewContentMode.left:
124 | NSLog("TODO: Eunomia UIImageView+Utility, imageDisplayFrame(): implement handling ContentMode")
125 | result = CGRect.zero
126 | break
127 |
128 | case UIViewContentMode.right:
129 | NSLog("TODO: Eunomia UIImageView+Utility, imageDisplayFrame(): implement handling ContentMode")
130 | result = CGRect.zero
131 | break
132 |
133 | case UIViewContentMode.redraw:
134 | NSLog("TODO: Eunomia UIImageView+Utility, imageDisplayFrame(): implement handling ContentMode")
135 | result = CGRect.zero
136 | break
137 | @unknown default:
138 | NSLog("TODO: Eunomia UIImageView+Utility, imageDisplayFrame(): implement handling ContentMode")
139 | result = CGRect.zero
140 | break
141 | }
142 |
143 | } else
144 | {
145 | result = CGRect.zero
146 | }
147 |
148 | return result
149 | }
150 | }
151 | #endif
152 |
--------------------------------------------------------------------------------
/Source/Core/UtilityExtensions/NSObject+Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSObject+Utility.swift
3 | // Eunomia
4 | //
5 | // Created by Ian Grossberg on 7/29/15.
6 | // Copyright (c) 2015 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | import ObjectiveC
13 |
14 | // rewrite inspired by https://github.com/tokorom/HasAssociatedObjects
15 | private class AssociatedPropertyObjects: NSObject {
16 |
17 | var values: [String: AnyObject] = [:]
18 |
19 | subscript(key: String) -> AnyObject? {
20 | get {
21 | return self.values[key]
22 | }
23 | set {
24 | self.values[key] = newValue
25 | }
26 | }
27 | }
28 |
29 | private var AssociatedObjectsKey: UInt8 = 0
30 |
31 | extension NSObject {
32 |
33 | fileprivate var associatedObjects: AssociatedPropertyObjects {
34 | guard let associatedObjects = objc_getAssociatedObject(self, &AssociatedObjectsKey) as? AssociatedPropertyObjects else {
35 | let associatedObjects = AssociatedPropertyObjects()
36 | objc_setAssociatedObject(self, &AssociatedObjectsKey, associatedObjects, .OBJC_ASSOCIATION_RETAIN)
37 | return associatedObjects
38 | }
39 | return associatedObjects
40 | }
41 |
42 | public func setAssociatedRetainProperty(_ key : String, value : AnyObject?) {
43 | self.associatedObjects[key] = value
44 | }
45 |
46 | public func getAssociatedProperty(_ key : String) -> T? {
47 | guard let valueUncasted = self.associatedObjects[key] else {
48 | return nil
49 | }
50 |
51 | guard let valueCasted = valueUncasted as? T else {
52 |
53 | Log.debug(message: "In \(self) for Key \(key) found Value \(valueUncasted), that could not be casted to Type \(T.self)")
54 | return nil
55 | }
56 |
57 | return valueCasted
58 | }
59 |
60 | public func getAssociatedProperty(_ key : String, fallback : T) -> T {
61 | guard let value : T = self.getAssociatedProperty(key) else {
62 | return fallback
63 | }
64 |
65 | return value
66 | }
67 | }
68 |
69 | #if os(iOS)
70 | @objc
71 | protocol KeyboardNotificationsSubscriber {
72 | @objc optional func alignWithKeyboard(_ beginFrame : CGRect, endFrame : CGRect, duration : TimeInterval, animationCurve : UIView.AnimationCurve)
73 | }
74 |
75 | extension NSObject {
76 |
77 | // TODO: because of how selectors are referenced in Swift and that we can't easily add a property to our NSObject extension in Swift is this safer to keep in Objective-C?
78 | public func registerForKeyboardNotifications() {
79 | NotificationCenter.default.addObserver(self, selector: NSSelectorFromString("keyboardWillShow:"), name: UIResponder.keyboardWillShowNotification, object: nil)
80 | NotificationCenter.default.addObserver(self, selector: NSSelectorFromString("keyboardDidShow:"), name: UIResponder.keyboardDidShowNotification, object: nil)
81 | NotificationCenter.default.addObserver(self, selector: NSSelectorFromString("keyboardWillHide:"), name: UIResponder.keyboardWillHideNotification, object: nil)
82 | NotificationCenter.default.addObserver(self, selector: NSSelectorFromString("keyboardDidHide:"), name: UIResponder.keyboardDidHideNotification, object: nil)
83 | }
84 |
85 | public func unregisterForKeyboardNotifications() {
86 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
87 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidShowNotification, object: nil)
88 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
89 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidHideNotification, object: nil)
90 | }
91 |
92 | internal class func getKeyboardChangeInfoValues(_ notification : Notification, beginFrame : inout CGRect?, endFrame : inout CGRect?, duration : inout TimeInterval?, animationCurve : inout UIView.AnimationCurve?) {
93 |
94 | beginFrame = notification.objectFromInfo(UIResponder.keyboardFrameBeginUserInfoKey)?.cgRectValue
95 | endFrame = notification.objectFromInfo(UIResponder.keyboardFrameEndUserInfoKey)?.cgRectValue
96 | duration = notification.objectFromInfo(UIResponder.keyboardAnimationDurationUserInfoKey)?.doubleValue
97 | if let curveValue = notification.objectFromInfo(UIResponder.keyboardAnimationCurveUserInfoKey) as? NSNumber
98 | {
99 | animationCurve = UIView.AnimationCurve(rawValue: curveValue.intValue)
100 | }
101 | }
102 |
103 | @objc open func keyboardWillShow(_ notification : Notification) {
104 | var beginFrame : CGRect?
105 | var endFrame : CGRect?
106 | var duration : TimeInterval?
107 | var animationCurve : UIView.AnimationCurve?
108 | NSObject.getKeyboardChangeInfoValues(notification, beginFrame: &beginFrame, endFrame: &endFrame, duration: &duration, animationCurve: &animationCurve)
109 |
110 | if let beginFrame = beginFrame,
111 | let endFrame = endFrame,
112 | let duration = duration,
113 | let animationCurve = animationCurve
114 | {
115 | self.keyboardWillShow(beginFrame, endFrame: endFrame, duration: duration, animationCurve: animationCurve)
116 | } else
117 | {
118 | Log.error(message: "Cannot call keyboardWillShow with info values, unable to retrieve expected info value")
119 | }
120 | }
121 |
122 | @objc open func keyboardWillShow(_ beginFrame : CGRect, endFrame : CGRect, duration : TimeInterval, animationCurve : UIView.AnimationCurve) {
123 | self.alignWithKeyboard(beginFrame, endFrame: endFrame, duration: duration, animationCurve: animationCurve)
124 | }
125 |
126 | @objc open func keyboardDidShow(_ notification : Notification) {
127 | var beginFrame : CGRect?
128 | var endFrame : CGRect?
129 | var duration : TimeInterval?
130 | var animationCurve : UIView.AnimationCurve?
131 | NSObject.getKeyboardChangeInfoValues(notification, beginFrame: &beginFrame, endFrame: &endFrame, duration: &duration, animationCurve: &animationCurve)
132 |
133 | if let beginFrame = beginFrame,
134 | let endFrame = endFrame,
135 | let duration = duration,
136 | let animationCurve = animationCurve
137 | {
138 | self.keyboardDidShow(beginFrame, endFrame: endFrame, duration: duration, animationCurve: animationCurve)
139 | } else
140 | {
141 | Log.error(message: "Cannot call keyboardDidShow with info values, unable to retrieve expected info value")
142 | }
143 | }
144 |
145 | @objc open func keyboardDidShow(_ beginFrame : CGRect, endFrame : CGRect, duration : TimeInterval, animationCurve : UIView.AnimationCurve) {
146 | self.alignWithKeyboard(beginFrame, endFrame: endFrame, duration: duration, animationCurve: animationCurve)
147 | }
148 |
149 | @objc open func keyboardWillHide(_ notification : Notification) {
150 | var beginFrame : CGRect?
151 | var endFrame : CGRect?
152 | var duration : TimeInterval?
153 | var animationCurve : UIView.AnimationCurve?
154 | NSObject.getKeyboardChangeInfoValues(notification, beginFrame: &beginFrame, endFrame: &endFrame, duration: &duration, animationCurve: &animationCurve)
155 |
156 | if let beginFrame = beginFrame,
157 | let endFrame = endFrame,
158 | let duration = duration,
159 | let animationCurve = animationCurve
160 | {
161 | self.keyboardWillHide(beginFrame, endFrame: endFrame, duration: duration, animationCurve: animationCurve)
162 | } else
163 | {
164 | Log.error(message: "Cannot call keyboardWillHide with info values, unable to retrieve expected info value")
165 | }
166 | }
167 |
168 | @objc open func keyboardWillHide(_ beginFrame : CGRect, endFrame : CGRect, duration : TimeInterval, animationCurve : UIView.AnimationCurve) {
169 | self.alignWithKeyboard(beginFrame, endFrame: endFrame, duration: duration, animationCurve: animationCurve)
170 | }
171 |
172 | @objc open func keyboardDidHide(_ notification : Notification) {
173 | var beginFrame : CGRect?
174 | var endFrame : CGRect?
175 | var duration : TimeInterval?
176 | var animationCurve : UIView.AnimationCurve?
177 | NSObject.getKeyboardChangeInfoValues(notification, beginFrame: &beginFrame, endFrame: &endFrame, duration: &duration, animationCurve: &animationCurve)
178 |
179 | if let beginFrame = beginFrame,
180 | let endFrame = endFrame,
181 | let duration = duration,
182 | let animationCurve = animationCurve
183 | {
184 | self.keyboardDidHide(beginFrame, endFrame: endFrame, duration: duration, animationCurve: animationCurve)
185 | } else
186 | {
187 | Log.error(message: "Cannot call keyboardDidHide with info values, unable to retrieve expected info value")
188 | }
189 | }
190 |
191 | @objc open func keyboardDidHide(_ beginFrame : CGRect, endFrame : CGRect, duration : TimeInterval, animationCurve : UIView.AnimationCurve) {
192 | self.alignWithKeyboard(beginFrame, endFrame: endFrame, duration: duration, animationCurve: animationCurve)
193 | }
194 |
195 | @objc open func alignWithKeyboard(_ beginFrame : CGRect, endFrame : CGRect, duration : TimeInterval, animationCurve : UIView.AnimationCurve) {
196 | }
197 | }
198 | #endif
199 |
--------------------------------------------------------------------------------
/Source/Core/App/Location.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Location.swift
3 | // Tracks->Eunomia
4 | //
5 | // Created by Ian Grossberg on 8/9/18.
6 | // Copyright © 2018 Adorkable. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | #if os(iOS)
11 | import UIKit
12 | #endif
13 | import CoreLocation
14 |
15 | /// Subscribes to Location Authorization status
16 | public protocol LocationAuthorizationStatusSubscriber: NSObjectProtocol {
17 | /// Called when authorization status changes
18 | ///
19 | /// - Parameters:
20 | /// - manager: Location manager
21 | /// - status: new status
22 | func location(_ manager: Location, status: CLAuthorizationStatus)
23 | }
24 |
25 | /// Subscribes to location and movement information
26 | public protocol LocationSubscriber: NSObjectProtocol {
27 | // TODO: protocol methods consistently named
28 |
29 | /// Raw callback information from iOS location manager
30 | ///
31 | /// - Parameters:
32 | /// - manager: Location manager instance
33 | /// - locations: raw information
34 | func location(_ manager: Location, locations: [CLLocation])
35 | /// Called when location changes
36 | ///
37 | /// - Parameters:
38 | /// - manager: Location manager instance
39 | /// - location: new location
40 | func location(_ manager: Location, location: CLLocation)
41 | /// Called when heading changes
42 | ///
43 | /// - Parameters:
44 | /// - manager: Location manager instance
45 | /// - didUpdateHeading: Updated heading
46 | func location(_ manager: Location, didUpdateHeading: CLHeading)
47 | }
48 |
49 | // MARK: - Optionals Extension for Swift
50 | public extension LocationSubscriber {
51 | func location(_ manager: Location, locations: [CLLocation]) {}
52 | func location(_ manager: Location, location: CLLocation) {}
53 | func location(_ manager: Location, didUpdateHeading: CLHeading) {}
54 | }
55 |
56 | /// Location manager interprets the raw iOS location information into more useful information and notifies any number of subscribers
57 | public class Location: NSObject {
58 | /// Main instance
59 | public static let main: Location = Location()
60 |
61 | private var locationManager = CLLocationManager() {
62 | didSet {
63 | self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
64 | self.locationManager.delegate = self
65 | }
66 | }
67 |
68 | private var _status: CLAuthorizationStatus = .notDetermined
69 | /// Current location authorization status
70 | public var status: CLAuthorizationStatus {
71 | return self._status
72 | }
73 |
74 | private var authorizationStatusSubscribers: [LocationAuthorizationStatusSubscriber] = []
75 | private var locationSubscribers: [LocationSubscriber] = []
76 |
77 | /// Current Location if it has been retrieved
78 | public var location: CLLocation? {
79 | return self.locationManager.location
80 | }
81 |
82 | #if os(iOS)
83 | /// Current Heading if it has been retrieved
84 | public var heading: CLHeading? {
85 | return self.locationManager.heading
86 | }
87 | #endif
88 |
89 | /// Set desired accuracy
90 | public var desiredAccuracy: CLLocationAccuracy {
91 | get {
92 | return self.locationManager.desiredAccuracy
93 | }
94 | set {
95 | self.locationManager.desiredAccuracy = newValue
96 | }
97 | }
98 |
99 | #if os(iOS)
100 | /// Set activity type
101 | public var activityType: CLActivityType {
102 | get {
103 | return self.locationManager.activityType
104 | }
105 | set {
106 | self.locationManager.activityType = newValue
107 | }
108 | }
109 | #endif
110 |
111 | override public init() {
112 | self._status = CLLocationManager.authorizationStatus()
113 |
114 | super.init()
115 |
116 | self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
117 | self.locationManager.delegate = self
118 | }
119 | }
120 |
121 | #if os(iOS)
122 | public extension Location {
123 | /// Add an authorization subscriber
124 | ///
125 | /// - Parameter subscriber: subscriber to add
126 | func add(subscriber: LocationAuthorizationStatusSubscriber) {
127 | self.authorizationStatusSubscribers.append(subscriber)
128 | }
129 |
130 | /// Remove an authorization subscriber
131 | ///
132 | /// - Parameter subscriber: subscriber to remove
133 | func remove(subscriber: LocationAuthorizationStatusSubscriber) {
134 | guard let index = self.authorizationStatusSubscribers.firstIndex(where: { (compare) -> Bool in
135 | return compare === subscriber
136 | }) else {
137 | return
138 | }
139 | self.authorizationStatusSubscribers.remove(at: index)
140 | }
141 |
142 | /// Force ask for "Always Authorizated" location access
143 | ///
144 | /// - Returns: `true` if authorization has been requested, `false` otherwise, usually if authorization has already been given or denied
145 | func askForAlwaysAuthorization() -> Bool {
146 | switch self._status {
147 | case .notDetermined:
148 | self.locationManager.requestAlwaysAuthorization()
149 | return true
150 |
151 | case .restricted, .denied:
152 | return false
153 |
154 | case .authorizedWhenInUse, .authorizedAlways:
155 | return false
156 |
157 | @unknown default:
158 | // TODO: log
159 | return false
160 | }
161 | }
162 |
163 | /// Force ask for "When In Use Authorizated" location access
164 | ///
165 | /// - Returns: `true` if authorization has been requested, `false` otherwise, usually if authorization has already been given or denied
166 | func askForWhenInUseAuthorization() -> Bool {
167 | switch self._status {
168 | case .notDetermined:
169 | self.locationManager.requestWhenInUseAuthorization()
170 | return true
171 |
172 | case .restricted, .denied:
173 | return false
174 |
175 | case .authorizedWhenInUse, .authorizedAlways:
176 | return false
177 |
178 | @unknown default:
179 | // TODO: log
180 | return false
181 | }
182 | }
183 | }
184 | #endif
185 |
186 | #if os(iOS)
187 | public extension Location {
188 | /// Show location assistance explanation pop up
189 | /// Since iOS only allows us requesting authorization one time make sure we don't burn it without explaining the reason for requesting
190 | /// To localize message add values to keys:
191 | /// - title: "Enable Location"
192 | /// - message: "This application requires you to enable Location features on your phone to function.\n\nPlease press Allow when asked to Allow Access"
193 | ///
194 | /// - Parameters:
195 | /// - over: View controller to show the pop up over
196 | /// - animated: Whether to animate the pop up
197 | /// - onPresentFinished: Called when presenting the explination is complete
198 | /// - onOk: Called when the user presses Ok
199 | static func showLocationAssistanceExplanationPopup(over: UIViewController, animated: Bool, onPresentFinished: (() -> Void)? = nil, onOk: (() -> Void)? = nil) {
200 | // TODO: allow for cancel? and/or support built in autodetecting already denied and prompt
201 | // TODO: direct customizing of title and message
202 | let alertController = UIAlertController(
203 | title: NSLocalizedString("Enable Location", comment: "Explanation popup title"),
204 | message: NSLocalizedString("This application requires you to enable Location features on your phone to function.\n\nPlease press Allow when asked to Allow Access",
205 | comment: "Explanation popup explanation"),
206 | preferredStyle: .alert)
207 | alertController.addAction(
208 | UIAlertAction(
209 | title: NSLocalizedString("Ok", comment: "Explanation popup Yes"),
210 | style: .default,
211 | handler: { (_) in
212 | onOk?()
213 | })
214 | )
215 |
216 | over.present(alertController, animated: animated, completion: onPresentFinished)
217 | }
218 | }
219 | #endif
220 |
221 | extension Location {
222 | /// Start tracking location
223 | public func startUpdatingLocation() {
224 | self.locationManager.startUpdatingLocation()
225 | }
226 |
227 | /// Stop tracking location
228 | public func stopUpdatingLocation() {
229 | self.locationManager.stopUpdatingLocation()
230 | }
231 |
232 | public func startUpdatingOnlySignifigantLocationChanges() -> Bool {
233 | guard !CLLocationManager.significantLocationChangeMonitoringAvailable() else {
234 | // The service is not available.
235 | // TODO: switch to exception throw
236 | return false
237 | }
238 |
239 | self.locationManager.startMonitoringSignificantLocationChanges()
240 |
241 | return true
242 | }
243 |
244 | public func stopUpdatingOnlySignifigantLocationChanges() {
245 | self.locationManager.stopMonitoringSignificantLocationChanges()
246 | }
247 |
248 | #if os(iOS)
249 | /// Start tracking heading
250 | public func startUpdatingHeading() {
251 | self.locationManager.startUpdatingHeading()
252 | }
253 |
254 | /// Stop tracking heading
255 | public func stopUpdatingHeading() {
256 | self.locationManager.stopUpdatingHeading()
257 | }
258 | #endif
259 | }
260 |
261 | public extension Location {
262 | /// Add a location and heading subscriber
263 | ///
264 | /// - Parameter subscriber: Subscriber to add
265 | func add(subscriber: LocationSubscriber) {
266 | self.locationSubscribers.append(subscriber)
267 | }
268 |
269 | /// Remove a location and heading subscriber
270 | ///
271 | /// - Parameter subscriber: Subscriber to remove
272 | func remove(subscriber: LocationSubscriber) {
273 | guard let index = self.locationSubscribers.firstIndex(where: { (compare) -> Bool in
274 | return compare === subscriber
275 | }) else {
276 | return
277 | }
278 | self.locationSubscribers.remove(at: index)
279 | }
280 |
281 | /// Do something for each location and heading subscriber
282 | ///
283 | /// - Parameter forEachDo: Closure to do for each subscriber
284 | /// - Throws: When a closure throws
285 | func forEach(_ forEachDo: (LocationSubscriber) throws -> Void) rethrows {
286 | try self.locationSubscribers.forEach(forEachDo)
287 | }
288 | }
289 |
290 | extension Location: CLLocationManagerDelegate {
291 | /// iOS Location Manager authorization changed, processes and passes to authorization subscribers
292 | ///
293 | /// - Parameters:
294 | /// - manager: iOS Location Manager
295 | /// - status: now authorization status
296 | public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
297 | self._status = status
298 | for subscriber in self.authorizationStatusSubscribers {
299 | subscriber.location(self, status: status)
300 | }
301 | }
302 |
303 | /// iOS location Manager location changed, processes and passes to location and heading subscribers
304 | ///
305 | /// - Parameters:
306 | /// - manager: iOS Location manager
307 | /// - locations: new locations
308 | public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
309 | self.handle(didUpdate: locations)
310 | }
311 |
312 | #if os(iOS)
313 | /// iOS location Manager heading changed, processes and passes to location and heading subscribers
314 | ///
315 | /// - Parameters:
316 | /// - manager: iOS Location manager
317 | /// - locations: new heading
318 | public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
319 | self.handle(didUpdate: newHeading)
320 | }
321 | #endif
322 | }
323 |
324 | extension Location {
325 | private func handle(didUpdate: [CLLocation]) {
326 | for subscriber in self.locationSubscribers {
327 | subscriber.location(self, locations: didUpdate)
328 | if let location = didUpdate.first {
329 | subscriber.location(self, location: location)
330 | }
331 | }
332 | }
333 |
334 | private func handle(didUpdate: CLHeading) {
335 | for subscriber in self.locationSubscribers {
336 | subscriber.location(self, didUpdateHeading: didUpdate)
337 | }
338 | }
339 | }
340 |
341 | public extension Location {
342 | static func placemark(from location: CLLocation, completionHandler: @escaping CLGeocodeCompletionHandler) {
343 | let geocoder = CLGeocoder()
344 |
345 | geocoder.reverseGeocodeLocation(location, completionHandler: completionHandler)
346 | }
347 |
348 | static func placemark(from location: CLLocation, completionHandler: @escaping (CLPlacemark?, Error?) -> Void) {
349 | self.placemark(from: location) { (placemarks: [CLPlacemark]?, error) in
350 | let firstLocation = placemarks?.first
351 | completionHandler(firstLocation, error)
352 | }
353 | }
354 | }
355 |
--------------------------------------------------------------------------------
/CocoapodTests/Eunomia.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 2A7029FB8916BA178854C3A1 /* Pods_EunomiaCocoapodiOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD83A43683222A1D5B0A9ACF /* Pods_EunomiaCocoapodiOSTests.framework */; };
11 | 3CB2C7851C2B1E3000BBC529 /* NSCharacterSet+UtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB2C7801C2B1E3000BBC529 /* NSCharacterSet+UtilityTests.swift */; };
12 | 3CB2C7861C2B1E3000BBC529 /* RandomTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB2C7811C2B1E3000BBC529 /* RandomTests.swift */; };
13 | 3CB2C7871C2B1E3000BBC529 /* String+UtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB2C7821C2B1E3000BBC529 /* String+UtilityTests.swift */; };
14 | 3CB2C7881C2B1E3000BBC529 /* SystemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB2C7831C2B1E3000BBC529 /* SystemTests.swift */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXFileReference section */
18 | 3CB2C7761C2B1DDE00BBC529 /* EunomiaCocoapodiOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EunomiaCocoapodiOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
19 | 3CB2C77F1C2B1E3000BBC529 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
20 | 3CB2C7801C2B1E3000BBC529 /* NSCharacterSet+UtilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSCharacterSet+UtilityTests.swift"; sourceTree = ""; };
21 | 3CB2C7811C2B1E3000BBC529 /* RandomTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomTests.swift; sourceTree = ""; };
22 | 3CB2C7821C2B1E3000BBC529 /* String+UtilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+UtilityTests.swift"; sourceTree = ""; };
23 | 3CB2C7831C2B1E3000BBC529 /* SystemTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemTests.swift; sourceTree = ""; };
24 | 8E283395F15B41F177929A1D /* Pods-EunomiaCocoapodiOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EunomiaCocoapodiOSTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-EunomiaCocoapodiOSTests/Pods-EunomiaCocoapodiOSTests.debug.xcconfig"; sourceTree = ""; };
25 | CAA2F5EC6A99269C4310AF60 /* Pods-EunomiaCocoapodiOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EunomiaCocoapodiOSTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-EunomiaCocoapodiOSTests/Pods-EunomiaCocoapodiOSTests.release.xcconfig"; sourceTree = ""; };
26 | CD83A43683222A1D5B0A9ACF /* Pods_EunomiaCocoapodiOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_EunomiaCocoapodiOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
27 | /* End PBXFileReference section */
28 |
29 | /* Begin PBXFrameworksBuildPhase section */
30 | 3CB2C7731C2B1DDE00BBC529 /* Frameworks */ = {
31 | isa = PBXFrameworksBuildPhase;
32 | buildActionMask = 2147483647;
33 | files = (
34 | 2A7029FB8916BA178854C3A1 /* Pods_EunomiaCocoapodiOSTests.framework in Frameworks */,
35 | );
36 | runOnlyForDeploymentPostprocessing = 0;
37 | };
38 | /* End PBXFrameworksBuildPhase section */
39 |
40 | /* Begin PBXGroup section */
41 | 3CB2C75D1C2B1D3700BBC529 = {
42 | isa = PBXGroup;
43 | children = (
44 | 3CB2C77E1C2B1E3000BBC529 /* Tests */,
45 | 3CB2C7681C2B1D3700BBC529 /* Products */,
46 | 83E76D21F6B691155DCAA114 /* Pods */,
47 | E97D945E4F2D844451B138ED /* Frameworks */,
48 | );
49 | sourceTree = "";
50 | };
51 | 3CB2C7681C2B1D3700BBC529 /* Products */ = {
52 | isa = PBXGroup;
53 | children = (
54 | 3CB2C7761C2B1DDE00BBC529 /* EunomiaCocoapodiOSTests.xctest */,
55 | );
56 | name = Products;
57 | sourceTree = "";
58 | };
59 | 3CB2C77E1C2B1E3000BBC529 /* Tests */ = {
60 | isa = PBXGroup;
61 | children = (
62 | 3CB2C77F1C2B1E3000BBC529 /* Info.plist */,
63 | 3CB2C7801C2B1E3000BBC529 /* NSCharacterSet+UtilityTests.swift */,
64 | 3CB2C7811C2B1E3000BBC529 /* RandomTests.swift */,
65 | 3CB2C7821C2B1E3000BBC529 /* String+UtilityTests.swift */,
66 | 3CB2C7831C2B1E3000BBC529 /* SystemTests.swift */,
67 | );
68 | name = Tests;
69 | path = ../Tests;
70 | sourceTree = "";
71 | };
72 | 83E76D21F6B691155DCAA114 /* Pods */ = {
73 | isa = PBXGroup;
74 | children = (
75 | 8E283395F15B41F177929A1D /* Pods-EunomiaCocoapodiOSTests.debug.xcconfig */,
76 | CAA2F5EC6A99269C4310AF60 /* Pods-EunomiaCocoapodiOSTests.release.xcconfig */,
77 | );
78 | name = Pods;
79 | sourceTree = "";
80 | };
81 | E97D945E4F2D844451B138ED /* Frameworks */ = {
82 | isa = PBXGroup;
83 | children = (
84 | CD83A43683222A1D5B0A9ACF /* Pods_EunomiaCocoapodiOSTests.framework */,
85 | );
86 | name = Frameworks;
87 | sourceTree = "";
88 | };
89 | /* End PBXGroup section */
90 |
91 | /* Begin PBXNativeTarget section */
92 | 3CB2C7751C2B1DDE00BBC529 /* EunomiaCocoapodiOSTests */ = {
93 | isa = PBXNativeTarget;
94 | buildConfigurationList = 3CB2C77B1C2B1DDE00BBC529 /* Build configuration list for PBXNativeTarget "EunomiaCocoapodiOSTests" */;
95 | buildPhases = (
96 | 4850748839E5757CB9DA9DD8 /* [CP] Check Pods Manifest.lock */,
97 | 3CB2C7721C2B1DDE00BBC529 /* Sources */,
98 | 3CB2C7731C2B1DDE00BBC529 /* Frameworks */,
99 | 3CB2C7741C2B1DDE00BBC529 /* Resources */,
100 | 8823B2174DF04EE6FD5A8B6D /* [CP] Embed Pods Frameworks */,
101 | C74681A5AE00B93579858C7F /* [CP] Copy Pods Resources */,
102 | );
103 | buildRules = (
104 | );
105 | dependencies = (
106 | );
107 | name = EunomiaCocoapodiOSTests;
108 | productName = EunomiaCocoapodsiOSTests;
109 | productReference = 3CB2C7761C2B1DDE00BBC529 /* EunomiaCocoapodiOSTests.xctest */;
110 | productType = "com.apple.product-type.bundle.unit-test";
111 | };
112 | /* End PBXNativeTarget section */
113 |
114 | /* Begin PBXProject section */
115 | 3CB2C75E1C2B1D3700BBC529 /* Project object */ = {
116 | isa = PBXProject;
117 | attributes = {
118 | LastSwiftUpdateCheck = 0720;
119 | LastUpgradeCheck = 0720;
120 | ORGANIZATIONNAME = Adorkable;
121 | TargetAttributes = {
122 | 3CB2C7751C2B1DDE00BBC529 = {
123 | CreatedOnToolsVersion = 7.2;
124 | LastSwiftMigration = 0800;
125 | };
126 | };
127 | };
128 | buildConfigurationList = 3CB2C7611C2B1D3700BBC529 /* Build configuration list for PBXProject "Eunomia" */;
129 | compatibilityVersion = "Xcode 3.2";
130 | developmentRegion = English;
131 | hasScannedForEncodings = 0;
132 | knownRegions = (
133 | en,
134 | );
135 | mainGroup = 3CB2C75D1C2B1D3700BBC529;
136 | productRefGroup = 3CB2C7681C2B1D3700BBC529 /* Products */;
137 | projectDirPath = "";
138 | projectRoot = "";
139 | targets = (
140 | 3CB2C7751C2B1DDE00BBC529 /* EunomiaCocoapodiOSTests */,
141 | );
142 | };
143 | /* End PBXProject section */
144 |
145 | /* Begin PBXResourcesBuildPhase section */
146 | 3CB2C7741C2B1DDE00BBC529 /* Resources */ = {
147 | isa = PBXResourcesBuildPhase;
148 | buildActionMask = 2147483647;
149 | files = (
150 | );
151 | runOnlyForDeploymentPostprocessing = 0;
152 | };
153 | /* End PBXResourcesBuildPhase section */
154 |
155 | /* Begin PBXShellScriptBuildPhase section */
156 | 4850748839E5757CB9DA9DD8 /* [CP] Check Pods Manifest.lock */ = {
157 | isa = PBXShellScriptBuildPhase;
158 | buildActionMask = 2147483647;
159 | files = (
160 | );
161 | inputPaths = (
162 | );
163 | name = "[CP] Check Pods Manifest.lock";
164 | outputPaths = (
165 | );
166 | runOnlyForDeploymentPostprocessing = 0;
167 | shellPath = /bin/sh;
168 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
169 | showEnvVarsInLog = 0;
170 | };
171 | 8823B2174DF04EE6FD5A8B6D /* [CP] Embed Pods Frameworks */ = {
172 | isa = PBXShellScriptBuildPhase;
173 | buildActionMask = 2147483647;
174 | files = (
175 | );
176 | inputPaths = (
177 | );
178 | name = "[CP] Embed Pods Frameworks";
179 | outputPaths = (
180 | );
181 | runOnlyForDeploymentPostprocessing = 0;
182 | shellPath = /bin/sh;
183 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-EunomiaCocoapodiOSTests/Pods-EunomiaCocoapodiOSTests-frameworks.sh\"\n";
184 | showEnvVarsInLog = 0;
185 | };
186 | C74681A5AE00B93579858C7F /* [CP] Copy Pods Resources */ = {
187 | isa = PBXShellScriptBuildPhase;
188 | buildActionMask = 2147483647;
189 | files = (
190 | );
191 | inputPaths = (
192 | );
193 | name = "[CP] Copy Pods Resources";
194 | outputPaths = (
195 | );
196 | runOnlyForDeploymentPostprocessing = 0;
197 | shellPath = /bin/sh;
198 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-EunomiaCocoapodiOSTests/Pods-EunomiaCocoapodiOSTests-resources.sh\"\n";
199 | showEnvVarsInLog = 0;
200 | };
201 | /* End PBXShellScriptBuildPhase section */
202 |
203 | /* Begin PBXSourcesBuildPhase section */
204 | 3CB2C7721C2B1DDE00BBC529 /* Sources */ = {
205 | isa = PBXSourcesBuildPhase;
206 | buildActionMask = 2147483647;
207 | files = (
208 | 3CB2C7881C2B1E3000BBC529 /* SystemTests.swift in Sources */,
209 | 3CB2C7861C2B1E3000BBC529 /* RandomTests.swift in Sources */,
210 | 3CB2C7871C2B1E3000BBC529 /* String+UtilityTests.swift in Sources */,
211 | 3CB2C7851C2B1E3000BBC529 /* NSCharacterSet+UtilityTests.swift in Sources */,
212 | );
213 | runOnlyForDeploymentPostprocessing = 0;
214 | };
215 | /* End PBXSourcesBuildPhase section */
216 |
217 | /* Begin XCBuildConfiguration section */
218 | 3CB2C76D1C2B1D3700BBC529 /* Debug */ = {
219 | isa = XCBuildConfiguration;
220 | buildSettings = {
221 | ALWAYS_SEARCH_USER_PATHS = NO;
222 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
223 | CLANG_CXX_LIBRARY = "libc++";
224 | CLANG_ENABLE_MODULES = YES;
225 | CLANG_ENABLE_OBJC_ARC = YES;
226 | CLANG_WARN_BOOL_CONVERSION = YES;
227 | CLANG_WARN_CONSTANT_CONVERSION = YES;
228 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
229 | CLANG_WARN_EMPTY_BODY = YES;
230 | CLANG_WARN_ENUM_CONVERSION = YES;
231 | CLANG_WARN_INT_CONVERSION = YES;
232 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
233 | CLANG_WARN_UNREACHABLE_CODE = YES;
234 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
235 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
236 | COPY_PHASE_STRIP = NO;
237 | CURRENT_PROJECT_VERSION = 1;
238 | DEBUG_INFORMATION_FORMAT = dwarf;
239 | ENABLE_STRICT_OBJC_MSGSEND = YES;
240 | ENABLE_TESTABILITY = YES;
241 | GCC_C_LANGUAGE_STANDARD = gnu99;
242 | GCC_DYNAMIC_NO_PIC = NO;
243 | GCC_NO_COMMON_BLOCKS = YES;
244 | GCC_OPTIMIZATION_LEVEL = 0;
245 | GCC_PREPROCESSOR_DEFINITIONS = (
246 | "DEBUG=1",
247 | "$(inherited)",
248 | );
249 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
250 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
251 | GCC_WARN_UNDECLARED_SELECTOR = YES;
252 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
253 | GCC_WARN_UNUSED_FUNCTION = YES;
254 | GCC_WARN_UNUSED_VARIABLE = YES;
255 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
256 | MTL_ENABLE_DEBUG_INFO = YES;
257 | ONLY_ACTIVE_ARCH = YES;
258 | SDKROOT = iphoneos;
259 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
260 | TARGETED_DEVICE_FAMILY = "1,2";
261 | VERSIONING_SYSTEM = "apple-generic";
262 | VERSION_INFO_PREFIX = "";
263 | };
264 | name = Debug;
265 | };
266 | 3CB2C76E1C2B1D3700BBC529 /* Release */ = {
267 | isa = XCBuildConfiguration;
268 | buildSettings = {
269 | ALWAYS_SEARCH_USER_PATHS = NO;
270 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
271 | CLANG_CXX_LIBRARY = "libc++";
272 | CLANG_ENABLE_MODULES = YES;
273 | CLANG_ENABLE_OBJC_ARC = YES;
274 | CLANG_WARN_BOOL_CONVERSION = YES;
275 | CLANG_WARN_CONSTANT_CONVERSION = YES;
276 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
277 | CLANG_WARN_EMPTY_BODY = YES;
278 | CLANG_WARN_ENUM_CONVERSION = YES;
279 | CLANG_WARN_INT_CONVERSION = YES;
280 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
281 | CLANG_WARN_UNREACHABLE_CODE = YES;
282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
283 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
284 | COPY_PHASE_STRIP = NO;
285 | CURRENT_PROJECT_VERSION = 1;
286 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
287 | ENABLE_NS_ASSERTIONS = NO;
288 | ENABLE_STRICT_OBJC_MSGSEND = YES;
289 | GCC_C_LANGUAGE_STANDARD = gnu99;
290 | GCC_NO_COMMON_BLOCKS = YES;
291 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
292 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
293 | GCC_WARN_UNDECLARED_SELECTOR = YES;
294 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
295 | GCC_WARN_UNUSED_FUNCTION = YES;
296 | GCC_WARN_UNUSED_VARIABLE = YES;
297 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
298 | MTL_ENABLE_DEBUG_INFO = NO;
299 | SDKROOT = iphoneos;
300 | TARGETED_DEVICE_FAMILY = "1,2";
301 | VALIDATE_PRODUCT = YES;
302 | VERSIONING_SYSTEM = "apple-generic";
303 | VERSION_INFO_PREFIX = "";
304 | };
305 | name = Release;
306 | };
307 | 3CB2C77C1C2B1DDE00BBC529 /* Debug */ = {
308 | isa = XCBuildConfiguration;
309 | baseConfigurationReference = 8E283395F15B41F177929A1D /* Pods-EunomiaCocoapodiOSTests.debug.xcconfig */;
310 | buildSettings = {
311 | INFOPLIST_FILE = ../Tests/Info.plist;
312 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
313 | PRODUCT_BUNDLE_IDENTIFIER = com.adorkable.eunomia.EunomiaCocoapodsiOSTests;
314 | PRODUCT_NAME = "$(TARGET_NAME)";
315 | SWIFT_VERSION = 2.3;
316 | };
317 | name = Debug;
318 | };
319 | 3CB2C77D1C2B1DDE00BBC529 /* Release */ = {
320 | isa = XCBuildConfiguration;
321 | baseConfigurationReference = CAA2F5EC6A99269C4310AF60 /* Pods-EunomiaCocoapodiOSTests.release.xcconfig */;
322 | buildSettings = {
323 | INFOPLIST_FILE = ../Tests/Info.plist;
324 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
325 | PRODUCT_BUNDLE_IDENTIFIER = com.adorkable.eunomia.EunomiaCocoapodsiOSTests;
326 | PRODUCT_NAME = "$(TARGET_NAME)";
327 | SWIFT_VERSION = 2.3;
328 | };
329 | name = Release;
330 | };
331 | /* End XCBuildConfiguration section */
332 |
333 | /* Begin XCConfigurationList section */
334 | 3CB2C7611C2B1D3700BBC529 /* Build configuration list for PBXProject "Eunomia" */ = {
335 | isa = XCConfigurationList;
336 | buildConfigurations = (
337 | 3CB2C76D1C2B1D3700BBC529 /* Debug */,
338 | 3CB2C76E1C2B1D3700BBC529 /* Release */,
339 | );
340 | defaultConfigurationIsVisible = 0;
341 | defaultConfigurationName = Release;
342 | };
343 | 3CB2C77B1C2B1DDE00BBC529 /* Build configuration list for PBXNativeTarget "EunomiaCocoapodiOSTests" */ = {
344 | isa = XCConfigurationList;
345 | buildConfigurations = (
346 | 3CB2C77C1C2B1DDE00BBC529 /* Debug */,
347 | 3CB2C77D1C2B1DDE00BBC529 /* Release */,
348 | );
349 | defaultConfigurationIsVisible = 0;
350 | defaultConfigurationName = Release;
351 | };
352 | /* End XCConfigurationList section */
353 | };
354 | rootObject = 3CB2C75E1C2B1D3700BBC529 /* Project object */;
355 | }
356 |
--------------------------------------------------------------------------------
/CarthageTests/Eunomia.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 3CB2C7851C2B1E3000BBC529 /* NSCharacterSet+UtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB2C7801C2B1E3000BBC529 /* NSCharacterSet+UtilityTests.swift */; };
11 | 3CB2C7861C2B1E3000BBC529 /* RandomTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB2C7811C2B1E3000BBC529 /* RandomTests.swift */; };
12 | 3CB2C7871C2B1E3000BBC529 /* String+UtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB2C7821C2B1E3000BBC529 /* String+UtilityTests.swift */; };
13 | 3CB2C7881C2B1E3000BBC529 /* SystemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB2C7831C2B1E3000BBC529 /* SystemTests.swift */; };
14 | 3CC9640F1EE883170017C90D /* Eunomia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CC9640E1EE883170017C90D /* Eunomia.framework */; };
15 | 3CC964111EE883230017C90D /* CocoaLumberjackSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CC964101EE883230017C90D /* CocoaLumberjackSwift.framework */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXFileReference section */
19 | 3CB2C7761C2B1DDE00BBC529 /* EunomiaCarthageiOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EunomiaCarthageiOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
20 | 3CB2C77F1C2B1E3000BBC529 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
21 | 3CB2C7801C2B1E3000BBC529 /* NSCharacterSet+UtilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSCharacterSet+UtilityTests.swift"; sourceTree = ""; };
22 | 3CB2C7811C2B1E3000BBC529 /* RandomTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomTests.swift; sourceTree = ""; };
23 | 3CB2C7821C2B1E3000BBC529 /* String+UtilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+UtilityTests.swift"; sourceTree = ""; };
24 | 3CB2C7831C2B1E3000BBC529 /* SystemTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemTests.swift; sourceTree = ""; };
25 | 3CC964051EE881620017C90D /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile; sourceTree = ""; };
26 | 3CC9640C1EE882B60017C90D /* CocoaLumberjack.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CocoaLumberjack.framework; path = Carthage/Build/iOS/CocoaLumberjack.framework; sourceTree = ""; };
27 | 3CC9640E1EE883170017C90D /* Eunomia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Eunomia.framework; path = Carthage/Build/iOS/Eunomia.framework; sourceTree = ""; };
28 | 3CC964101EE883230017C90D /* CocoaLumberjackSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CocoaLumberjackSwift.framework; path = Carthage/Build/iOS/CocoaLumberjackSwift.framework; sourceTree = ""; };
29 | /* End PBXFileReference section */
30 |
31 | /* Begin PBXFrameworksBuildPhase section */
32 | 3CB2C7731C2B1DDE00BBC529 /* Frameworks */ = {
33 | isa = PBXFrameworksBuildPhase;
34 | buildActionMask = 2147483647;
35 | files = (
36 | 3CC9640F1EE883170017C90D /* Eunomia.framework in Frameworks */,
37 | 3CC964111EE883230017C90D /* CocoaLumberjackSwift.framework in Frameworks */,
38 | );
39 | runOnlyForDeploymentPostprocessing = 0;
40 | };
41 | /* End PBXFrameworksBuildPhase section */
42 |
43 | /* Begin PBXGroup section */
44 | 3CB2C75D1C2B1D3700BBC529 = {
45 | isa = PBXGroup;
46 | children = (
47 | 3CC964051EE881620017C90D /* Cartfile */,
48 | 3CB2C77E1C2B1E3000BBC529 /* Tests */,
49 | 3CB2C7681C2B1D3700BBC529 /* Products */,
50 | 3CC9640B1EE882B60017C90D /* Frameworks */,
51 | );
52 | sourceTree = "";
53 | };
54 | 3CB2C7681C2B1D3700BBC529 /* Products */ = {
55 | isa = PBXGroup;
56 | children = (
57 | 3CB2C7761C2B1DDE00BBC529 /* EunomiaCarthageiOSTests.xctest */,
58 | );
59 | name = Products;
60 | sourceTree = "";
61 | };
62 | 3CB2C77E1C2B1E3000BBC529 /* Tests */ = {
63 | isa = PBXGroup;
64 | children = (
65 | 3CB2C77F1C2B1E3000BBC529 /* Info.plist */,
66 | 3CB2C7801C2B1E3000BBC529 /* NSCharacterSet+UtilityTests.swift */,
67 | 3CB2C7811C2B1E3000BBC529 /* RandomTests.swift */,
68 | 3CB2C7821C2B1E3000BBC529 /* String+UtilityTests.swift */,
69 | 3CB2C7831C2B1E3000BBC529 /* SystemTests.swift */,
70 | );
71 | name = Tests;
72 | path = ../Tests;
73 | sourceTree = "";
74 | };
75 | 3CC9640B1EE882B60017C90D /* Frameworks */ = {
76 | isa = PBXGroup;
77 | children = (
78 | 3CC964101EE883230017C90D /* CocoaLumberjackSwift.framework */,
79 | 3CC9640E1EE883170017C90D /* Eunomia.framework */,
80 | 3CC9640C1EE882B60017C90D /* CocoaLumberjack.framework */,
81 | );
82 | name = Frameworks;
83 | sourceTree = "";
84 | };
85 | /* End PBXGroup section */
86 |
87 | /* Begin PBXLegacyTarget section */
88 | 3CC964071EE881960017C90D /* carthage */ = {
89 | isa = PBXLegacyTarget;
90 | buildArgumentsString = "bootstrap --configuration Debug --platform iOS --use-submodules --use-ssh --verbose --color auto ";
91 | buildConfigurationList = 3CC964081EE881960017C90D /* Build configuration list for PBXLegacyTarget "carthage" */;
92 | buildPhases = (
93 | );
94 | buildToolPath = /usr/local/bin/carthage;
95 | buildWorkingDirectory = $PROJECT_DIR;
96 | dependencies = (
97 | );
98 | name = carthage;
99 | passBuildSettingsInEnvironment = 1;
100 | productName = carthage;
101 | };
102 | /* End PBXLegacyTarget section */
103 |
104 | /* Begin PBXNativeTarget section */
105 | 3CB2C7751C2B1DDE00BBC529 /* EunomiaCarthageiOSTests */ = {
106 | isa = PBXNativeTarget;
107 | buildConfigurationList = 3CB2C77B1C2B1DDE00BBC529 /* Build configuration list for PBXNativeTarget "EunomiaCarthageiOSTests" */;
108 | buildPhases = (
109 | 3CB2C7721C2B1DDE00BBC529 /* Sources */,
110 | 3CB2C7731C2B1DDE00BBC529 /* Frameworks */,
111 | 3CB2C7741C2B1DDE00BBC529 /* Resources */,
112 | C74681A5AE00B93579858C7F /* Embed Carthage Frameworks */,
113 | );
114 | buildRules = (
115 | );
116 | dependencies = (
117 | );
118 | name = EunomiaCarthageiOSTests;
119 | productName = EunomiaCocoapodsiOSTests;
120 | productReference = 3CB2C7761C2B1DDE00BBC529 /* EunomiaCarthageiOSTests.xctest */;
121 | productType = "com.apple.product-type.bundle.unit-test";
122 | };
123 | /* End PBXNativeTarget section */
124 |
125 | /* Begin PBXProject section */
126 | 3CB2C75E1C2B1D3700BBC529 /* Project object */ = {
127 | isa = PBXProject;
128 | attributes = {
129 | LastSwiftUpdateCheck = 0720;
130 | LastUpgradeCheck = 0830;
131 | ORGANIZATIONNAME = Adorkable;
132 | TargetAttributes = {
133 | 3CB2C7751C2B1DDE00BBC529 = {
134 | CreatedOnToolsVersion = 7.2;
135 | DevelopmentTeam = 3UDY2NT62F;
136 | LastSwiftMigration = 0830;
137 | };
138 | 3CC964071EE881960017C90D = {
139 | CreatedOnToolsVersion = 8.3.2;
140 | DevelopmentTeam = 3UDY2NT62F;
141 | ProvisioningStyle = Automatic;
142 | };
143 | };
144 | };
145 | buildConfigurationList = 3CB2C7611C2B1D3700BBC529 /* Build configuration list for PBXProject "Eunomia" */;
146 | compatibilityVersion = "Xcode 3.2";
147 | developmentRegion = English;
148 | hasScannedForEncodings = 0;
149 | knownRegions = (
150 | en,
151 | );
152 | mainGroup = 3CB2C75D1C2B1D3700BBC529;
153 | productRefGroup = 3CB2C7681C2B1D3700BBC529 /* Products */;
154 | projectDirPath = "";
155 | projectRoot = "";
156 | targets = (
157 | 3CB2C7751C2B1DDE00BBC529 /* EunomiaCarthageiOSTests */,
158 | 3CC964071EE881960017C90D /* carthage */,
159 | );
160 | };
161 | /* End PBXProject section */
162 |
163 | /* Begin PBXResourcesBuildPhase section */
164 | 3CB2C7741C2B1DDE00BBC529 /* Resources */ = {
165 | isa = PBXResourcesBuildPhase;
166 | buildActionMask = 2147483647;
167 | files = (
168 | );
169 | runOnlyForDeploymentPostprocessing = 0;
170 | };
171 | /* End PBXResourcesBuildPhase section */
172 |
173 | /* Begin PBXShellScriptBuildPhase section */
174 | C74681A5AE00B93579858C7F /* Embed Carthage Frameworks */ = {
175 | isa = PBXShellScriptBuildPhase;
176 | buildActionMask = 2147483647;
177 | files = (
178 | );
179 | inputPaths = (
180 | "$(SRCROOT)/Carthage/Build/iOS/Eunomia.framework",
181 | "$(SRCROOT)/Carthage/Build/iOS/CocoaLumberjackSwift.framework",
182 | );
183 | name = "Embed Carthage Frameworks";
184 | outputPaths = (
185 | );
186 | runOnlyForDeploymentPostprocessing = 0;
187 | shellPath = /bin/sh;
188 | shellScript = "/usr/local/bin/carthage copy-frameworks";
189 | showEnvVarsInLog = 0;
190 | };
191 | /* End PBXShellScriptBuildPhase section */
192 |
193 | /* Begin PBXSourcesBuildPhase section */
194 | 3CB2C7721C2B1DDE00BBC529 /* Sources */ = {
195 | isa = PBXSourcesBuildPhase;
196 | buildActionMask = 2147483647;
197 | files = (
198 | 3CB2C7881C2B1E3000BBC529 /* SystemTests.swift in Sources */,
199 | 3CB2C7861C2B1E3000BBC529 /* RandomTests.swift in Sources */,
200 | 3CB2C7871C2B1E3000BBC529 /* String+UtilityTests.swift in Sources */,
201 | 3CB2C7851C2B1E3000BBC529 /* NSCharacterSet+UtilityTests.swift in Sources */,
202 | );
203 | runOnlyForDeploymentPostprocessing = 0;
204 | };
205 | /* End PBXSourcesBuildPhase section */
206 |
207 | /* Begin XCBuildConfiguration section */
208 | 3CB2C76D1C2B1D3700BBC529 /* Debug */ = {
209 | isa = XCBuildConfiguration;
210 | buildSettings = {
211 | ALWAYS_SEARCH_USER_PATHS = NO;
212 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
213 | CLANG_CXX_LIBRARY = "libc++";
214 | CLANG_ENABLE_MODULES = YES;
215 | CLANG_ENABLE_OBJC_ARC = YES;
216 | CLANG_WARN_BOOL_CONVERSION = YES;
217 | CLANG_WARN_CONSTANT_CONVERSION = YES;
218 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
219 | CLANG_WARN_EMPTY_BODY = YES;
220 | CLANG_WARN_ENUM_CONVERSION = YES;
221 | CLANG_WARN_INFINITE_RECURSION = YES;
222 | CLANG_WARN_INT_CONVERSION = YES;
223 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
224 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
225 | CLANG_WARN_UNREACHABLE_CODE = YES;
226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
227 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
228 | COPY_PHASE_STRIP = NO;
229 | CURRENT_PROJECT_VERSION = 1;
230 | DEBUG_INFORMATION_FORMAT = dwarf;
231 | ENABLE_STRICT_OBJC_MSGSEND = YES;
232 | ENABLE_TESTABILITY = YES;
233 | GCC_C_LANGUAGE_STANDARD = gnu99;
234 | GCC_DYNAMIC_NO_PIC = NO;
235 | GCC_NO_COMMON_BLOCKS = YES;
236 | GCC_OPTIMIZATION_LEVEL = 0;
237 | GCC_PREPROCESSOR_DEFINITIONS = (
238 | "DEBUG=1",
239 | "$(inherited)",
240 | );
241 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
242 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
243 | GCC_WARN_UNDECLARED_SELECTOR = YES;
244 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
245 | GCC_WARN_UNUSED_FUNCTION = YES;
246 | GCC_WARN_UNUSED_VARIABLE = YES;
247 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
248 | MTL_ENABLE_DEBUG_INFO = YES;
249 | ONLY_ACTIVE_ARCH = YES;
250 | SDKROOT = iphoneos;
251 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
252 | TARGETED_DEVICE_FAMILY = "1,2";
253 | VERSIONING_SYSTEM = "apple-generic";
254 | VERSION_INFO_PREFIX = "";
255 | };
256 | name = Debug;
257 | };
258 | 3CB2C76E1C2B1D3700BBC529 /* Release */ = {
259 | isa = XCBuildConfiguration;
260 | buildSettings = {
261 | ALWAYS_SEARCH_USER_PATHS = NO;
262 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
263 | CLANG_CXX_LIBRARY = "libc++";
264 | CLANG_ENABLE_MODULES = YES;
265 | CLANG_ENABLE_OBJC_ARC = YES;
266 | CLANG_WARN_BOOL_CONVERSION = YES;
267 | CLANG_WARN_CONSTANT_CONVERSION = YES;
268 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
269 | CLANG_WARN_EMPTY_BODY = YES;
270 | CLANG_WARN_ENUM_CONVERSION = YES;
271 | CLANG_WARN_INFINITE_RECURSION = YES;
272 | CLANG_WARN_INT_CONVERSION = YES;
273 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
274 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
275 | CLANG_WARN_UNREACHABLE_CODE = YES;
276 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
277 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
278 | COPY_PHASE_STRIP = NO;
279 | CURRENT_PROJECT_VERSION = 1;
280 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
281 | ENABLE_NS_ASSERTIONS = NO;
282 | ENABLE_STRICT_OBJC_MSGSEND = YES;
283 | GCC_C_LANGUAGE_STANDARD = gnu99;
284 | GCC_NO_COMMON_BLOCKS = YES;
285 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
286 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
287 | GCC_WARN_UNDECLARED_SELECTOR = YES;
288 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
289 | GCC_WARN_UNUSED_FUNCTION = YES;
290 | GCC_WARN_UNUSED_VARIABLE = YES;
291 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
292 | MTL_ENABLE_DEBUG_INFO = NO;
293 | SDKROOT = iphoneos;
294 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
295 | TARGETED_DEVICE_FAMILY = "1,2";
296 | VALIDATE_PRODUCT = YES;
297 | VERSIONING_SYSTEM = "apple-generic";
298 | VERSION_INFO_PREFIX = "";
299 | };
300 | name = Release;
301 | };
302 | 3CB2C77C1C2B1DDE00BBC529 /* Debug */ = {
303 | isa = XCBuildConfiguration;
304 | buildSettings = {
305 | DEVELOPMENT_TEAM = 3UDY2NT62F;
306 | FRAMEWORK_SEARCH_PATHS = (
307 | "$(inherited)",
308 | "$(PROJECT_DIR)/Carthage/Build/iOS",
309 | );
310 | INFOPLIST_FILE = ../Tests/Info.plist;
311 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(FRAMEWORK_SEARCH_PATHS)";
312 | PRODUCT_BUNDLE_IDENTIFIER = com.adorkable.eunomia.EunomiaCocoapodsiOSTests;
313 | PRODUCT_NAME = "$(TARGET_NAME)";
314 | SWIFT_VERSION = 3.0;
315 | };
316 | name = Debug;
317 | };
318 | 3CB2C77D1C2B1DDE00BBC529 /* Release */ = {
319 | isa = XCBuildConfiguration;
320 | buildSettings = {
321 | DEVELOPMENT_TEAM = 3UDY2NT62F;
322 | FRAMEWORK_SEARCH_PATHS = (
323 | "$(inherited)",
324 | "$(PROJECT_DIR)/Carthage/Build/iOS",
325 | );
326 | GCC_OPTIMIZATION_LEVEL = 0;
327 | INFOPLIST_FILE = ../Tests/Info.plist;
328 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(FRAMEWORK_SEARCH_PATHS)";
329 | PRODUCT_BUNDLE_IDENTIFIER = com.adorkable.eunomia.EunomiaCocoapodsiOSTests;
330 | PRODUCT_NAME = "$(TARGET_NAME)";
331 | SWIFT_VERSION = 3.0;
332 | };
333 | name = Release;
334 | };
335 | 3CC964091EE881960017C90D /* Debug */ = {
336 | isa = XCBuildConfiguration;
337 | buildSettings = {
338 | CLANG_ANALYZER_NONNULL = YES;
339 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
340 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
341 | CLANG_WARN_INFINITE_RECURSION = YES;
342 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
343 | DEBUGGING_SYMBOLS = YES;
344 | DEBUG_INFORMATION_FORMAT = dwarf;
345 | DEVELOPMENT_TEAM = 3UDY2NT62F;
346 | GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
347 | GCC_OPTIMIZATION_LEVEL = 0;
348 | OTHER_CFLAGS = "";
349 | OTHER_LDFLAGS = "";
350 | PRODUCT_NAME = "$(TARGET_NAME)";
351 | };
352 | name = Debug;
353 | };
354 | 3CC9640A1EE881960017C90D /* Release */ = {
355 | isa = XCBuildConfiguration;
356 | buildSettings = {
357 | CLANG_ANALYZER_NONNULL = YES;
358 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
359 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
360 | CLANG_WARN_INFINITE_RECURSION = YES;
361 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
362 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
363 | DEVELOPMENT_TEAM = 3UDY2NT62F;
364 | OTHER_CFLAGS = "";
365 | OTHER_LDFLAGS = "";
366 | PRODUCT_NAME = "$(TARGET_NAME)";
367 | };
368 | name = Release;
369 | };
370 | /* End XCBuildConfiguration section */
371 |
372 | /* Begin XCConfigurationList section */
373 | 3CB2C7611C2B1D3700BBC529 /* Build configuration list for PBXProject "Eunomia" */ = {
374 | isa = XCConfigurationList;
375 | buildConfigurations = (
376 | 3CB2C76D1C2B1D3700BBC529 /* Debug */,
377 | 3CB2C76E1C2B1D3700BBC529 /* Release */,
378 | );
379 | defaultConfigurationIsVisible = 0;
380 | defaultConfigurationName = Release;
381 | };
382 | 3CB2C77B1C2B1DDE00BBC529 /* Build configuration list for PBXNativeTarget "EunomiaCarthageiOSTests" */ = {
383 | isa = XCConfigurationList;
384 | buildConfigurations = (
385 | 3CB2C77C1C2B1DDE00BBC529 /* Debug */,
386 | 3CB2C77D1C2B1DDE00BBC529 /* Release */,
387 | );
388 | defaultConfigurationIsVisible = 0;
389 | defaultConfigurationName = Release;
390 | };
391 | 3CC964081EE881960017C90D /* Build configuration list for PBXLegacyTarget "carthage" */ = {
392 | isa = XCConfigurationList;
393 | buildConfigurations = (
394 | 3CC964091EE881960017C90D /* Debug */,
395 | 3CC9640A1EE881960017C90D /* Release */,
396 | );
397 | defaultConfigurationIsVisible = 0;
398 | defaultConfigurationName = Release;
399 | };
400 | /* End XCConfigurationList section */
401 | };
402 | rootObject = 3CB2C75E1C2B1D3700BBC529 /* Project object */;
403 | }
404 |
--------------------------------------------------------------------------------