├── 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 | " \nCanvas not supported\n" + 34 | " \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 | --------------------------------------------------------------------------------