├── .gitattributes ├── .gitignore ├── .swift-version ├── .travis.yml ├── Alexandria.playground ├── Contents.swift ├── Sources │ └── SupportCode.swift └── contents.xcplayground ├── Alexandria.podspec ├── Alexandria.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ ├── Alexandria.xcscheme │ └── AlexandriaTests.xcscheme ├── Alexandria.xcworkspace └── contents.xcworkspacedata ├── Alexandria ├── Alexandria.h └── Info.plist ├── AlexandriaTests ├── AlexandriaTests.swift ├── CGFloatTests.swift ├── CharacterSetTests.swift ├── CollectionTests.swift ├── ColorTests.swift ├── DateTests.swift ├── Info.plist ├── IntTests.swift ├── StringTests.swift └── URLTests.swift ├── LICENSE.txt ├── README.md ├── Sources ├── Array+Extensions.swift ├── CALayer+Extensions.swift ├── CAMediaTimingFunction+Extensions.swift ├── CGAffineTransform+Extensions.swift ├── CGFloat+Extensions.swift ├── CharacterSet+Extensions.swift ├── Collection+Extensions.swift ├── Comparable+Extensions.swift ├── CoreGraphics+Extensions.swift ├── Date+Extensions.swift ├── Dictionary+Extensions.swift ├── Double+Extensions.swift ├── Float+Extensions.swift ├── ImageEffects │ └── UIImage+Effects.swift ├── Int+Extensions.swift ├── NSAttributedString+Extensions.swift ├── NSObject+Customizable.swift ├── NSObject+Extensions.swift ├── Operators.swift ├── Optional+Extensions.swift ├── StoreKit │ └── SKProduct+Extensions.swift ├── String+Extensions.swift ├── UIAlertController+Extensions.swift ├── UIButton+Extensions.swift ├── UICollectionView+Extensions.swift ├── UIColor+Extensions.swift ├── UIControl+Actionable.swift ├── UIFont+Extensions.swift ├── UIGestureRecognizer+Extensions.swift ├── UIImage+Extensions.swift ├── UIScreen+Extensions.swift ├── UIScrollView+Extensions.swift ├── UISplitViewController+Extensions.swift ├── UITableView+Extensions.swift ├── UIView+Extensions.swift ├── UIViewController+Extensions.swift └── URL+Extensions.swift └── Support └── create_docs.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | Alexandria.podspec linguist-vendored 2 | Alexandria/Alexandria.h linguist-vendored 3 | Support/* linguist-vendored -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8 3 | rvm: 2.2.2 4 | install: 5 | - gem install activesupport --no-rdoc --no-ri --no-document 6 | - gem install jazzy --no-rdoc --no-ri --no-document 7 | notifications: 8 | email: 9 | - hsoi@hsoi.com 10 | - jlandon@me.com 11 | - ben.kreeger@icloud.com 12 | script: 13 | - xcodebuild -project Alexandria.xcodeproj -scheme Alexandria -sdk iphonesimulator10.0 -destination 'platform=iOS Simulator,name=iPhone 6,OS=10.0' test 14 | after_success: 15 | - ./Support/create_docs.sh 16 | env: 17 | global: 18 | secure: rXlDuY/rnCxgnPmSl2n1RubCvlATIH5pJMB39V5IW1WA7JrCTqCekKWGp5sSik1tu7xdkOENVHJz+a65KEeVHEDxU4KAv8ECRwulKQEO99mDSRmc5X+v5psBr3u3RQwNBZpRMHXKpvyKzuwp1tesjKoMHHppbCmlwQBv+17drMFlnuwnHLZ/NoYAVhB62qLGTtEoOtHKFgSLD9lzaTZL92J3XXTPXU+Wi46Lt6epTMg0ySyDOb49JsysL5edsnyixGw8ub1J2CrT6L0Hrxz1HNkv2i+AMrmCKsI7ZAJrdFHciUB/ARB8TPWOVMmIYtl25YjKFzS5i3K4mRoqsfHsAVbVuFY0ClOveze0VYC9mPNmTrgnObcn/AXkg3LxziJp5QdoRnR5PzbJvkE5l42zLBx3QThPcPB5ggZ5qn7wAiSVZaQT+cil14S2KqRdYp6t2xRUJwdEJ8okmmhBOVqsjRbzhg8+iXeTdqwGV7oHdijFFbwjJnI4weeA73ghP14OVkK6gi/E1Gqcxnil06y0Da3m4Z5B0kWdWyDAdSPVz3QnrV69sRdlFVd+v3yl701ikSOSdzbZf4sj+8HUdl5wWjGoQytAhqbuZd4YHPXHESh0GNYkUZV0y4aSrB1LZdR1NQX1n7Ws418AInm8BXH51/kX2efFjaZhfqprM2ezVxQ= 19 | -------------------------------------------------------------------------------- /Alexandria.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import Alexandria 4 | 5 | -------------------------------------------------------------------------------- /Alexandria.playground/Sources/SupportCode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // This file (and all other Swift source files in the Sources directory of this playground) will be precompiled into a framework which is automatically made available to Alexandria.playground. 3 | // 4 | -------------------------------------------------------------------------------- /Alexandria.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Alexandria.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Alexandria" 3 | s.version = "2.1.0" 4 | s.summary = "Oven Bits' library of useful iOS Swift extensions." 5 | s.description = <<-DESC 6 | The Oven Bits open source library, Alexandria, is one of the new Wonders of the iOS World. 7 | 8 | Alexandria provides a library of useful iOS Swift extensions that we have found useful in our 9 | iOS app development. We hope you'll find them useful as well. 10 | DESC 11 | 12 | s.homepage = "https://github.com/ovenbits/Alexandria" 13 | s.license = { :type => "MIT", :text => <<-LICENSE 14 | Copyright (c) 2014-2016 Oven Bits. 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy 17 | of this software and associated documentation files (the "Software"), to deal 18 | in the Software without restriction, including without limitation the rights 19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | copies of the Software, and to permit persons to whom the Software is 21 | furnished to do so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in 24 | all copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | THE SOFTWARE. 33 | LICENSE 34 | } 35 | s.author = { 36 | "John Daub" => "hsoi@hsoi.com", 37 | "Jonathan Landon" => "jlandon@me.com", 38 | "Ben Kreeger" => "ben.kreeger@icloud.com", 39 | "Chris Voss" => "chrisvoss@gmail.com", 40 | "Steven Schobert" => "spschobert@gmail.com", 41 | "Jayson Lane" => "jaysonlane@gmail.com", 42 | "Claire Knight" => "claire.knight@krider.co.uk", 43 | "Alex Corcoran" => "alex.p.corcoran@gmail.com" 44 | } 45 | s.social_media_url = 'https://twitter.com/ovenbitsmobile' 46 | s.platform = :ios, "8.0" 47 | s.requires_arc = true 48 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '3.0' } 49 | s.documentation_url = 'https://ovenbits.github.io/Alexandria' 50 | s.source = { :git => "https://github.com/ovenbits/Alexandria.git", :tag => "v#{s.version}" } 51 | s.frameworks = *%w(Foundation UIKit) 52 | 53 | s.subspec "Core" do |sp| 54 | sp.source_files = *%w(Sources/*.{swift,h,m}) 55 | end 56 | 57 | s.subspec "StoreKit" do |sp| 58 | sp.source_files = "Sources/StoreKit" 59 | sp.weak_framework = 'StoreKit' 60 | end 61 | 62 | s.subspec "ImageEffects" do |sp| 63 | sp.source_files = "Sources/ImageEffects" 64 | sp.weak_framework = 'Accelerate' 65 | end 66 | 67 | end 68 | -------------------------------------------------------------------------------- /Alexandria.xcodeproj/xcshareddata/xcschemes/Alexandria.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /Alexandria.xcodeproj/xcshareddata/xcschemes/AlexandriaTests.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 | -------------------------------------------------------------------------------- /Alexandria.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Alexandria/Alexandria.h: -------------------------------------------------------------------------------- 1 | // 2 | // Alexandria.h 3 | // 4 | // Created by Jonathan Landon on 7/29/15. 5 | // 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Oven Bits, LLC 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | 29 | #import 30 | 31 | //! Project version number for Alexandria. 32 | FOUNDATION_EXPORT double AlexandriaVersionNumber; 33 | 34 | //! Project version string for Alexandria. 35 | FOUNDATION_EXPORT const unsigned char AlexandriaVersionString[]; 36 | 37 | // In this header, you should import all the public headers of your framework using statements like #import 38 | 39 | -------------------------------------------------------------------------------- /Alexandria/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /AlexandriaTests/AlexandriaTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlexandriaTests.swift 3 | // AlexandriaTests 4 | // 5 | // Created by hsoi on 2/13/16. 6 | // 7 | // 8 | 9 | import XCTest 10 | 11 | class AlexandriaTests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func testExample() { 24 | // This is an example of a functional test case. 25 | // Use XCTAssert and related functions to verify your tests produce the correct results. 26 | } 27 | 28 | func testPerformanceExample() { 29 | // This is an example of a performance test case. 30 | self.measureBlock { 31 | // Put the code you want to measure the time of here. 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /AlexandriaTests/CGFloatTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGFloatTests.swift 3 | // Alexandria 4 | // 5 | // Created by Jonathan Landon on 6/20/16. 6 | // 7 | // 8 | 9 | import XCTest 10 | 11 | class CGFloatTests: XCTestCase { 12 | 13 | func testRounded() { 14 | XCTAssertEqual((1.234567 as CGFloat).rounded(places: 0), 1) 15 | XCTAssertEqual((1.234567 as CGFloat).rounded(places: 1), 1.2) 16 | XCTAssertEqual((1.234567 as CGFloat).rounded(places: 2), 1.23) 17 | XCTAssertEqual((1.234567 as CGFloat).rounded(places: 3), 1.235) 18 | XCTAssertEqual((1.234567 as CGFloat).rounded(places: 4), 1.2346) 19 | XCTAssertEqual((1.234567 as CGFloat).rounded(places: 5), 1.23457) 20 | XCTAssertEqual((1.234567 as CGFloat).rounded(places: 6), 1.234567) 21 | XCTAssertEqual((1.234567 as CGFloat).rounded(places: 7), 1.234567) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /AlexandriaTests/CharacterSetTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacterSetTests.swift 3 | // 4 | // Created by Jonathan Landon on 10/26/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import XCTest 29 | 30 | class CharacterSetTests: XCTestCase { 31 | 32 | func testStringLiteralInitializer() { 33 | let set: CharacterSet = "abcdefg" 34 | 35 | XCTAssert(set.contains("a"), "Character missing from set") 36 | XCTAssert(set.contains("d"), "Character missing from set") 37 | XCTAssert(!set.contains("h"), "Character should not be in set") 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /AlexandriaTests/CollectionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionTests.swift 3 | // 4 | // Created by Jonathan Landon on 2/8/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import XCTest 29 | import Alexandria 30 | 31 | class CollectionTests: XCTestCase { 32 | 33 | override func setUp() { 34 | super.setUp() 35 | // Put setup code here. This method is called before the invocation of each test method in the class. 36 | } 37 | 38 | override func tearDown() { 39 | // Put teardown code here. This method is called after the invocation of each test method in the class. 40 | super.tearDown() 41 | } 42 | 43 | func testRemoveArrayElement() { 44 | var array = [1, 2, 3, 4, 5] 45 | 46 | XCTAssertEqual(array.remove(3), 3, "Element not found") 47 | XCTAssertEqual(array.remove(3), .none, "Element found") 48 | } 49 | 50 | func testUniqueArray() { 51 | let array = [1, 2, 4, 3, 2, 4, 5, 3] 52 | 53 | XCTAssertEqual(array.unique(), [1, 2, 4, 3, 5], "Unique arrays are not equal") 54 | } 55 | 56 | func testRotateArray() { 57 | var array = [1, 2, 3, 4, 5] 58 | 59 | XCTAssertEqual(array.rotated(by: array.count), array, "Array should have been rotated right") 60 | XCTAssertEqual(array.rotated(by: 1), [2, 3, 4, 5, 1], "Array should have been rotated right") 61 | XCTAssertEqual(array.rotated(by: 2), [3, 4, 5, 1, 2], "Array should have been rotated right") 62 | XCTAssertEqual(array.rotated(by: 0), array, "Array should not have been rotated") 63 | XCTAssertEqual(array.rotated(by: -2), [4, 5, 1, 2, 3], "Array should have been rotated left") 64 | XCTAssertEqual(array.rotated(by: -1), [5, 1, 2, 3, 4], "Array should have been rotated left") 65 | XCTAssertEqual(array.rotated(by: -1 * array.count), array, "Array should have been rotated left") 66 | 67 | array = [] 68 | XCTAssertEqual(array.rotated(by: 2), [], "Empty array cannot be rotated") 69 | } 70 | 71 | func testRotateInPlace() { 72 | var array = [1, 2, 3, 4, 5] 73 | array.rotate(by: 1) 74 | XCTAssertEqual([2, 3, 4, 5, 1], array, "Array should have been rotated right") 75 | array = [1, 2, 3, 4, 5] 76 | array.rotate(by: 2) 77 | XCTAssertEqual([3, 4, 5, 1, 2], array, "Array should have been rotated right") 78 | array = [1, 2, 3, 4, 5] 79 | array.rotate(by: 0) 80 | XCTAssertEqual(array, array, "Array should not have been rotated") 81 | array = [1, 2, 3, 4, 5] 82 | array.rotate(by: -2) 83 | XCTAssertEqual([4, 5, 1, 2, 3], array, "Array should have been rotated left") 84 | array = [1, 2, 3, 4, 5] 85 | array.rotate(by: -1) 86 | XCTAssertEqual([5, 1, 2, 3, 4], array, "Array should have been rotated left") 87 | array = [] 88 | array.rotate(by: 2) 89 | XCTAssertEqual(array, array, "Empty array cannot be rotated") 90 | } 91 | 92 | func testMapWithIndex() { 93 | let array = [1, 2, 3, 4, 5] 94 | 95 | let mapped: [Int] = array.mapWithIndex { index, element in 96 | return (index % 2 == 1) ? element * 2 : element 97 | } 98 | 99 | XCTAssertEqual(mapped, [1, 4, 3, 8, 5], "Mapping failed") 100 | } 101 | 102 | 103 | func testBefore() { 104 | let emptyArray = [Int]() 105 | 106 | XCTAssertNil(emptyArray.before(42)) 107 | 108 | let oneArray = [1] 109 | XCTAssertNil(oneArray.before(1)) 110 | XCTAssertNil(oneArray.before(42)) 111 | 112 | let twoArray = [1,2] 113 | XCTAssertNil(twoArray.before(1)) 114 | XCTAssertEqual(twoArray.before(2), 1) 115 | XCTAssertNil(twoArray.before(3)) 116 | 117 | let threeArray = [1,2,3] 118 | XCTAssertNil(threeArray.before(1)) 119 | XCTAssertEqual(threeArray.before(2), 1) 120 | XCTAssertEqual(threeArray.before(3), 2) 121 | XCTAssertNil(threeArray.before(4)) 122 | 123 | let tensArray = [10, 30, 50, 70, 90, 20, 40, 60, 80, 100] 124 | XCTAssertNil(tensArray.before(10)) 125 | XCTAssertEqual(tensArray.before(20), 90) 126 | XCTAssertEqual(tensArray.before(30), 10) 127 | XCTAssertEqual(tensArray.before(40), 20) 128 | XCTAssertEqual(tensArray.before(50), 30) 129 | XCTAssertEqual(tensArray.before(60), 40) 130 | XCTAssertEqual(tensArray.before(70), 50) 131 | XCTAssertEqual(tensArray.before(80), 60) 132 | XCTAssertEqual(tensArray.before(90), 70) 133 | XCTAssertEqual(tensArray.before(100), 80) 134 | XCTAssertNil(tensArray.before(110)) 135 | } 136 | 137 | 138 | func testAfter() { 139 | let emptyArray = [Int]() 140 | 141 | XCTAssertNil(emptyArray.after(42)) 142 | 143 | let oneArray = [1] 144 | XCTAssertNil(oneArray.after(0)) 145 | XCTAssertNil(oneArray.after(1)) 146 | 147 | let twoArray = [1,2] 148 | XCTAssertNil(twoArray.after(0)) 149 | XCTAssertEqual(twoArray.after(1), 2) 150 | XCTAssertNil(twoArray.after(2)) 151 | XCTAssertNil(twoArray.after(3)) 152 | 153 | let threeArray = [1,2,3] 154 | XCTAssertNil(threeArray.after(0)) 155 | XCTAssertEqual(threeArray.after(1), 2) 156 | XCTAssertEqual(threeArray.after(2), 3) 157 | XCTAssertNil(threeArray.after(3)) 158 | XCTAssertNil(threeArray.after(4)) 159 | 160 | let tensArray = [10, 30, 50, 70, 90, 20, 40, 60, 80, 100] 161 | XCTAssertNil(tensArray.after(0)) 162 | XCTAssertEqual(tensArray.after(10), 30) 163 | XCTAssertEqual(tensArray.after(20), 40) 164 | XCTAssertEqual(tensArray.after(30), 50) 165 | XCTAssertEqual(tensArray.after(40), 60) 166 | XCTAssertEqual(tensArray.after(50), 70) 167 | XCTAssertEqual(tensArray.after(60), 80) 168 | XCTAssertEqual(tensArray.after(70), 90) 169 | XCTAssertEqual(tensArray.after(80), 100) 170 | XCTAssertEqual(tensArray.after(90), 20) 171 | XCTAssertNil(tensArray.after(100)) 172 | } 173 | 174 | 175 | func testDictionaryUnion() { 176 | let dictionary1 = ["key1" : 1, "key2" : 2] 177 | let dictionary2 = ["key3" : 3] 178 | 179 | XCTAssertEqual(dictionary1.union(dictionary2), ["key1" : 1, "key2" : 2, "key3" : 3], "Dictionaries are not equal") 180 | XCTAssertEqual(dictionary1 + dictionary2, ["key1" : 1, "key2" : 2, "key3" : 3], "Dictionaries are not equal") 181 | } 182 | 183 | func testOptionalIndex() { 184 | let array = [1, 2, 3, 4, 5] 185 | 186 | XCTAssertEqual(array[safe:1], 2, "Array elements are not equal") 187 | XCTAssertEqual(array[safe:5], nil, "Array elements are not equal") 188 | 189 | XCTAssertEqual(array.at(2), 3, "Array elements are not equal") 190 | XCTAssertEqual(array.at(-1), nil, "Array elements are not equal") 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /AlexandriaTests/ColorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorTests.swift 3 | // 4 | // Created by Jonathan Landon on 2/8/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import XCTest 29 | import Alexandria 30 | 31 | class ColorTests: XCTestCase { 32 | 33 | override func setUp() { 34 | super.setUp() 35 | // Put setup code here. This method is called before the invocation of each test method in the class. 36 | } 37 | 38 | override func tearDown() { 39 | // Put teardown code here. This method is called after the invocation of each test method in the class. 40 | super.tearDown() 41 | } 42 | 43 | func testHexInitializer() { 44 | XCTAssertEqual(UIColor(hex: 0xFF0000), UIColor.red, "Colors not equal") 45 | XCTAssertEqual(UIColor(hexString: "#00FF00"), UIColor.green, "Colors not equal") 46 | XCTAssertEqual(UIColor(hexString: "0000FF"), UIColor.blue, "Colors not equal") 47 | XCTAssertEqual(UIColor(hue: 0, saturation: 1, lightness: 0.5), UIColor.red, "Colors not equal") 48 | XCTAssertEqual(UIColor(cyan: 0, magenta: 1, yellow: 1, key: 0), UIColor.red, "Colors not equal") 49 | } 50 | 51 | func testHexString() { 52 | XCTAssertEqual(UIColor.purple.hexString, "#7f007f", "Hex strings are not equal") 53 | } 54 | 55 | func testHex() { 56 | XCTAssertEqual(UIColor.yellow.hex, UIColor(hex: 0xFFFF00).hex, "Hex values are not equal") 57 | } 58 | 59 | func testComponents() { 60 | let color = UIColor(hex: 0xEF5138) 61 | 62 | XCTAssertCGFloatEqual(color.rgba.r, 0.937, roundedToDecimalPlaces: 3, "Red components are not equal") 63 | XCTAssertCGFloatEqual(color.rgba.g, 0.318, roundedToDecimalPlaces: 3, "Green components are not equal") 64 | XCTAssertCGFloatEqual(color.rgba.b, 0.220, roundedToDecimalPlaces: 3, "Blue components are not equal") 65 | } 66 | 67 | func testLightenColor() { 68 | let color = UIColor(hex: 0xEF5138) 69 | XCTAssertEqual(color.lightened(by: 0.2).hex, 0xF3816F) 70 | XCTAssertEqual(color.lightened(by: 20%).hex, 0xF3816F) 71 | } 72 | 73 | func testDarkenColor() { 74 | let color = UIColor(hex: 0xEF5138) 75 | XCTAssertEqual(color.darkened(by: 0.2).hex, 0xDA2D12) 76 | XCTAssertEqual(color.darkened(by: 20%).hex, 0xDA2D12) 77 | } 78 | 79 | func testRGBColorModel() { 80 | let rgb: [(r: CGFloat, g: CGFloat, b: CGFloat)] = [ 81 | (255, 255, 0), 82 | (255, 128, 0), 83 | (255, 0, 0), 84 | (0, 255, 0), 85 | (0, 128, 0), 86 | (0, 255, 255), 87 | (0, 0, 255), 88 | (170, 0, 255), 89 | (255, 0, 255), 90 | (153, 102, 0), 91 | (255, 255, 255), 92 | (242, 242, 242), 93 | (179, 179, 179), 94 | (102, 102, 102), 95 | (0, 0, 0), 96 | ] 97 | 98 | for color in rgb { 99 | let model = UIColor.Model.rgb(color.r, color.g, color.b) 100 | 101 | let hsl = UIColor.Model.hsl(model.hsl.h, model.hsl.s, model.hsl.l) 102 | let hsb = UIColor.Model.hsb(model.hsb.h, model.hsb.s, model.hsb.b) 103 | let cmyk = UIColor.Model.cmyk(model.cmyk.c, model.cmyk.m, model.cmyk.y, model.cmyk.k) 104 | let hex = UIColor.Model.hex(model.hex) 105 | 106 | XCTAssertEqual(hsl.rgb.r, color.r) 107 | XCTAssertEqual(hsl.rgb.g, color.g) 108 | XCTAssertEqual(hsl.rgb.b, color.b) 109 | 110 | XCTAssertEqual(hsb.rgb.r, color.r) 111 | XCTAssertEqual(hsb.rgb.g, color.g) 112 | XCTAssertEqual(hsb.rgb.b, color.b) 113 | 114 | XCTAssertEqual(cmyk.rgb.r, color.r) 115 | XCTAssertEqual(cmyk.rgb.g, color.g) 116 | XCTAssertEqual(cmyk.rgb.b, color.b) 117 | 118 | XCTAssertEqual(hex.rgb.r, color.r) 119 | XCTAssertEqual(hex.rgb.g, color.g) 120 | XCTAssertEqual(hex.rgb.b, color.b) 121 | } 122 | } 123 | 124 | func testhsbColorModel() { 125 | let hsb: [(h: CGFloat, s: CGFloat, b: CGFloat)] = [ 126 | (60/360, 1, 1), 127 | (30/360, 1, 1), 128 | (0, 1, 1), 129 | (120/360, 1, 1), 130 | (120/360, 1, 0.5), 131 | (180/360, 1, 1), 132 | (240/360, 1, 1), 133 | (280/360, 1, 1), 134 | (300/360, 1, 1), 135 | (40/360, 1, 0.6), 136 | (0, 0, 1), 137 | (0, 0, 0.95), 138 | (0, 0, 0.7), 139 | (0, 0, 0.4), 140 | (0, 0, 0), 141 | ] 142 | 143 | for color in hsb { 144 | let model = UIColor.Model.hsb(color.h, color.s, color.b) 145 | 146 | let rgb = UIColor.Model.rgb(model.rgb.r, model.rgb.g, model.rgb.b) 147 | let hsb = UIColor.Model.hsb(model.hsb.h, model.hsb.s, model.hsb.b) 148 | let cmyk = UIColor.Model.cmyk(model.cmyk.c, model.cmyk.m, model.cmyk.y, model.cmyk.k) 149 | let hex = UIColor.Model.hex(model.hex) 150 | 151 | XCTAssertCGFloatEqual(rgb.hsb.h, color.h, roundedToDecimalPlaces: 2, "Hue components are not equal") 152 | XCTAssertCGFloatEqual(rgb.hsb.s, color.s, roundedToDecimalPlaces: 2, "Saturation components are not equal") 153 | XCTAssertCGFloatEqual(rgb.hsb.b, color.b, roundedToDecimalPlaces: 2, "Brightness components are not equal") 154 | 155 | XCTAssertCGFloatEqual(hsb.hsb.h, color.h, roundedToDecimalPlaces: 2, "Hue components are not equal") 156 | XCTAssertCGFloatEqual(hsb.hsb.s, color.s, roundedToDecimalPlaces: 2, "Saturation components are not equal") 157 | XCTAssertCGFloatEqual(hsb.hsb.b, color.b, roundedToDecimalPlaces: 2, "Brightness components are not equal") 158 | 159 | XCTAssertCGFloatEqual(cmyk.hsb.h, color.h, roundedToDecimalPlaces: 2, "Hue components are not equal") 160 | XCTAssertCGFloatEqual(cmyk.hsb.s, color.s, roundedToDecimalPlaces: 2, "Saturation components are not equal") 161 | XCTAssertCGFloatEqual(cmyk.hsb.b, color.b, roundedToDecimalPlaces: 2, "Brightness components are not equal") 162 | 163 | XCTAssertCGFloatEqual(hex.hsb.h, color.h, roundedToDecimalPlaces: 2, "Hue components are not equal") 164 | XCTAssertCGFloatEqual(hex.hsb.s, color.s, roundedToDecimalPlaces: 2, "Saturation components are not equal") 165 | XCTAssertCGFloatEqual(hex.hsb.b, color.b, roundedToDecimalPlaces: 2, "Brightness components are not equal") 166 | } 167 | } 168 | 169 | func testHSLColorModel() { 170 | let hsl: [(h: CGFloat, s: CGFloat, l: CGFloat)] = [ 171 | (60/360, 1, 0.5), 172 | (30/360, 1, 0.5), 173 | (0, 1, 0.5), 174 | (120/360, 1, 0.5), 175 | (120/360, 1, 0.25), 176 | (180/360, 1, 0.5), 177 | (240/360, 1, 0.5), 178 | (280/360, 1, 0.5), 179 | (300/360, 1, 0.5), 180 | (40/360, 1, 0.3), 181 | (0, 0, 1), 182 | (0, 0, 0.95), 183 | (0, 0, 0.7), 184 | (0, 0, 0.4), 185 | (0, 0, 0), 186 | ] 187 | 188 | for color in hsl { 189 | let model = UIColor.Model.hsl(color.h, color.s, color.l) 190 | 191 | let rgb = UIColor.Model.rgb(model.rgb.r, model.rgb.g, model.rgb.b) 192 | let hsb = UIColor.Model.hsb(model.hsb.h, model.hsb.s, model.hsb.b) 193 | let cmyk = UIColor.Model.cmyk(model.cmyk.c, model.cmyk.m, model.cmyk.y, model.cmyk.k) 194 | let hex = UIColor.Model.hex(model.hex) 195 | 196 | XCTAssertCGFloatEqual(rgb.hsl.h, color.h, roundedToDecimalPlaces: 2, "Hue components are not equal") 197 | XCTAssertCGFloatEqual(rgb.hsl.s, color.s, roundedToDecimalPlaces: 2, "Saturation components are not equal") 198 | XCTAssertCGFloatEqual(rgb.hsl.l, color.l, roundedToDecimalPlaces: 2, "Lightness components are not equal") 199 | 200 | XCTAssertCGFloatEqual(hsb.hsl.h, color.h, roundedToDecimalPlaces: 2, "Hue components are not equal") 201 | XCTAssertCGFloatEqual(hsb.hsl.s, color.s, roundedToDecimalPlaces: 2, "Saturation components are not equal") 202 | XCTAssertCGFloatEqual(hsb.hsl.l, color.l, roundedToDecimalPlaces: 2, "Lightness components are not equal") 203 | 204 | XCTAssertCGFloatEqual(cmyk.hsl.h, color.h, roundedToDecimalPlaces: 2, "Hue components are not equal") 205 | XCTAssertCGFloatEqual(cmyk.hsl.s, color.s, roundedToDecimalPlaces: 2, "Saturation components are not equal") 206 | XCTAssertCGFloatEqual(cmyk.hsl.l, color.l, roundedToDecimalPlaces: 2, "Lightness components are not equal") 207 | 208 | XCTAssertCGFloatEqual(hex.hsl.h, color.h, roundedToDecimalPlaces: 2, "Hue components are not equal") 209 | XCTAssertCGFloatEqual(hex.hsl.s, color.s, roundedToDecimalPlaces: 2, "Saturation components are not equal") 210 | XCTAssertCGFloatEqual(hex.hsl.l, color.l, roundedToDecimalPlaces: 2, "Lightness components are not equal") 211 | } 212 | } 213 | 214 | func testCMYKColorModel() { 215 | let cmyk: [(c: CGFloat, m: CGFloat, y: CGFloat, k: CGFloat)] = [ 216 | (0, 0, 1, 0), 217 | (0, 0.5, 1, 0), 218 | (0, 1, 1, 0), 219 | (1, 0, 1, 0), 220 | (1, 0, 1, 0.5), 221 | (1, 0, 0, 0), 222 | (1, 1, 0, 0), 223 | (1/3, 1, 0, 0), 224 | (0, 1, 0, 0), 225 | (0, 1/3, 1, 0.4), 226 | (0, 0, 0, 0), 227 | (0, 0, 0, 0.05), 228 | (0, 0, 0, 0.3), 229 | (0, 0, 0, 0.6), 230 | (0, 0, 0, 1), 231 | ] 232 | 233 | for color in cmyk { 234 | let model = UIColor.Model.cmyk(color.c, color.m, color.y, color.k) 235 | 236 | let rgb = UIColor.Model.rgb(model.rgb.r, model.rgb.g, model.rgb.b) 237 | let hsl = UIColor.Model.hsl(model.hsl.h, model.hsl.s, model.hsl.l) 238 | let hsb = UIColor.Model.hsb(model.hsb.h, model.hsb.s, model.hsb.b) 239 | let hex = UIColor.Model.hex(model.hex) 240 | 241 | XCTAssertCGFloatEqual(rgb.cmyk.c, color.c, roundedToDecimalPlaces: 2, "Cyan components are not equal") 242 | XCTAssertCGFloatEqual(rgb.cmyk.m, color.m, roundedToDecimalPlaces: 2, "Magenta components are not equal") 243 | XCTAssertCGFloatEqual(rgb.cmyk.y, color.y, roundedToDecimalPlaces: 2, "Yellow components are not equal") 244 | XCTAssertCGFloatEqual(rgb.cmyk.k, color.k, roundedToDecimalPlaces: 2, "Key components are not equal") 245 | 246 | XCTAssertCGFloatEqual(hsl.cmyk.c, color.c, roundedToDecimalPlaces: 2, "Cyan components are not equal") 247 | XCTAssertCGFloatEqual(hsl.cmyk.m, color.m, roundedToDecimalPlaces: 2, "Magenta components are not equal") 248 | XCTAssertCGFloatEqual(hsl.cmyk.y, color.y, roundedToDecimalPlaces: 2, "Yellow components are not equal") 249 | XCTAssertCGFloatEqual(hsl.cmyk.k, color.k, roundedToDecimalPlaces: 2, "Key components are not equal") 250 | 251 | XCTAssertCGFloatEqual(hsb.cmyk.c, color.c, roundedToDecimalPlaces: 2, "Cyan components are not equal") 252 | XCTAssertCGFloatEqual(hsb.cmyk.m, color.m, roundedToDecimalPlaces: 2, "Magenta components are not equal") 253 | XCTAssertCGFloatEqual(hsb.cmyk.y, color.y, roundedToDecimalPlaces: 2, "Yellow components are not equal") 254 | XCTAssertCGFloatEqual(hsb.cmyk.k, color.k, roundedToDecimalPlaces: 2, "Key components are not equal") 255 | 256 | XCTAssertCGFloatEqual(hex.cmyk.c, color.c, roundedToDecimalPlaces: 2, "Cyan components are not equal") 257 | XCTAssertCGFloatEqual(hex.cmyk.m, color.m, roundedToDecimalPlaces: 2, "Magenta components are not equal") 258 | XCTAssertCGFloatEqual(hex.cmyk.y, color.y, roundedToDecimalPlaces: 2, "Yellow components are not equal") 259 | XCTAssertCGFloatEqual(hex.cmyk.k, color.k, roundedToDecimalPlaces: 2, "Key components are not equal") 260 | } 261 | } 262 | 263 | func testHexColorModel() { 264 | let hex: [UInt32] = [ 265 | 0xFFFF00, 266 | 0xFF8000, 267 | 0xFF0000, 268 | 0x00FF00, 269 | 0x008000, 270 | 0x00FFFF, 271 | 0x0000FF, 272 | 0xAA00FF, 273 | 0xFF00FF, 274 | 0x996600, 275 | 0xFFFFFF, 276 | 0xF3F3F3, 277 | 0xB3B3B3, 278 | 0x676767, 279 | 0x000000 280 | ] 281 | 282 | for color in hex { 283 | let model = UIColor.Model.hex(color) 284 | 285 | let rgb = UIColor.Model.rgb(model.rgb.r, model.rgb.g, model.rgb.b) 286 | let hsl = UIColor.Model.hsl(model.hsl.h, model.hsl.s, model.hsl.l) 287 | let hsb = UIColor.Model.hsb(model.hsb.h, model.hsb.s, model.hsb.b) 288 | let cmyk = UIColor.Model.cmyk(model.cmyk.c, model.cmyk.m, model.cmyk.y, model.cmyk.k) 289 | 290 | XCTAssertEqual(rgb.hex, color) 291 | XCTAssertEqual(rgb.hex, color) 292 | XCTAssertEqual(rgb.hex, color) 293 | XCTAssertEqual(rgb.hex, color) 294 | 295 | XCTAssertEqual(hsl.hex, color) 296 | XCTAssertEqual(hsl.hex, color) 297 | XCTAssertEqual(hsl.hex, color) 298 | XCTAssertEqual(hsl.hex, color) 299 | 300 | XCTAssertEqual(hsb.hex, color) 301 | XCTAssertEqual(hsb.hex, color) 302 | XCTAssertEqual(hsb.hex, color) 303 | XCTAssertEqual(hsb.hex, color) 304 | 305 | XCTAssertEqual(cmyk.hex, color) 306 | XCTAssertEqual(cmyk.hex, color) 307 | XCTAssertEqual(cmyk.hex, color) 308 | XCTAssertEqual(cmyk.hex, color) 309 | } 310 | } 311 | } 312 | 313 | func XCTAssertCGFloatEqual(_ lhs: CGFloat, _ rhs: CGFloat, roundedToDecimalPlaces places: UInt, _ message: String) { 314 | XCTAssertEqual(lhs.rounded(places: places), rhs.rounded(places: places), message) 315 | } 316 | -------------------------------------------------------------------------------- /AlexandriaTests/DateTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateTests.swift 3 | // 4 | // Created by Jonathan Landon on 2/8/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import XCTest 29 | 30 | class DateTests: XCTestCase { 31 | 32 | override func setUp() { 33 | super.setUp() 34 | // Put setup code here. This method is called before the invocation of each test method in the class. 35 | } 36 | 37 | override func tearDown() { 38 | // Put teardown code here. This method is called after the invocation of each test method in the class. 39 | super.tearDown() 40 | } 41 | 42 | func testTimeFormatting() { 43 | let calendar = Calendar.current 44 | 45 | let components = DateComponents.init(hour: 9, minute: 41, second: 0) 46 | 47 | let date = calendar.date(from: components) 48 | 49 | XCTAssertEqual(date?.time(), "09:41", "Times are not equal") 50 | XCTAssertEqual(date?.time(format: .hourMinutePeriod), "09:41 AM", "Times are not equal") 51 | XCTAssertEqual(date?.time(format: .hourMinuteSecondMilitary), "09:41:00", "Times are not equal") 52 | XCTAssertEqual(date?.time(format: .hourMinuteSecondPeriod), "09:41:00 AM", "Times are not equal") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /AlexandriaTests/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 | -------------------------------------------------------------------------------- /AlexandriaTests/IntTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntTests.swift 3 | // 4 | // Created by hsoi on 6/3/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import XCTest 29 | 30 | class IntTests: XCTestCase { 31 | 32 | override func setUp() { 33 | super.setUp() 34 | // Put setup code here. This method is called before the invocation of each test method in the class. 35 | } 36 | 37 | override func tearDown() { 38 | // Put teardown code here. This method is called after the invocation of each test method in the class. 39 | super.tearDown() 40 | } 41 | 42 | 43 | func testToAbbreviatedString() { 44 | XCTAssertEqual(Int(598).abbreviatedString, "598") 45 | XCTAssertEqual(Int(-999).abbreviatedString, "-999") 46 | XCTAssertEqual(Int(1000).abbreviatedString, "1K") 47 | XCTAssertEqual(Int(-1284).abbreviatedString, "-1.3K") 48 | XCTAssertEqual(Int(9940).abbreviatedString, "9.9K") 49 | XCTAssertEqual(Int(9980).abbreviatedString, "10K") 50 | XCTAssertEqual(Int(39900).abbreviatedString, "39.9K") 51 | XCTAssertEqual(Int(99880).abbreviatedString, "99.9K") 52 | XCTAssertEqual(Int(399880).abbreviatedString, "0.4M") 53 | XCTAssertEqual(Int(999898).abbreviatedString, "1M") 54 | XCTAssertEqual(Int(999999).abbreviatedString, "1M") 55 | XCTAssertEqual(Int(1456384).abbreviatedString, "1.5M") 56 | XCTAssertEqual(Int(12383474).abbreviatedString, "12.4M") 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /AlexandriaTests/StringTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringTests.swift 3 | // 4 | // Created by Jonathan Landon on 2/8/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import XCTest 29 | 30 | class StringTests: XCTestCase { 31 | 32 | override func setUp() { 33 | super.setUp() 34 | // Put setup code here. This method is called before the invocation of each test method in the class. 35 | } 36 | 37 | override func tearDown() { 38 | // Put teardown code here. This method is called after the invocation of each test method in the class. 39 | super.tearDown() 40 | } 41 | 42 | func testCamelCased() { 43 | XCTAssertEqual("os version".camelCased, "osVersion", "CamelCased strings are not equal") 44 | XCTAssertEqual("HelloWorld".camelCased, "helloWorld", "CamelCased strings are not equal") 45 | XCTAssertEqual("someword With Characters".camelCased, "somewordWithCharacters", "CamelCased strings are not equal") 46 | } 47 | 48 | func testBase64() { 49 | XCTAssertEqual("Oven Bits".base64Encoded, "T3ZlbiBCaXRz", "Base64 encoded strings are not equal") 50 | XCTAssertEqual("T3ZlbiBCaXRz".base64Decoded, "Oven Bits", "Base64 decoded strings are not equal") 51 | } 52 | 53 | func testRegex() { 54 | XCTAssertEqual("hello".regex("[aeiou]", "*"), "h*ll*", "Regex strings are not equal") 55 | XCTAssertEqual("hello".regex("([aeiou])", "<$1>"), "hll", "Regex strings are not equal") 56 | } 57 | 58 | func testRegexClosure() { 59 | XCTAssertEqual("hello".regex(".") { 60 | let s = $0.unicodeScalars 61 | let v = s[s.startIndex].value 62 | return "\(v) " 63 | }, "104 101 108 108 111 ", "Regex closure strings are not equal") 64 | } 65 | 66 | func testRanges() { 67 | XCTAssertEqual("swift"[0...1], "sw", "String ranges are not equal") 68 | } 69 | 70 | func testTruncate() { 71 | XCTAssertEqual("hello there".truncated(to: 5), "hello", "Truncated strings are not equal") 72 | XCTAssertEqual("hello there".truncated(to: 5, trailing: "..."), "hello...", "Truncated strings are not equal") 73 | } 74 | 75 | func testNumeric() { 76 | XCTAssert("1".isNumeric, "Simple string of one digit is numberic") 77 | XCTAssert("12345678901234567890".isNumeric, "String of many digits is numeric") 78 | XCTAssertFalse("".isNumeric, "Empty string is not numeric") 79 | XCTAssertFalse("12.34".isNumeric, "String containing decimal point is not numeric") 80 | XCTAssertFalse("a".isNumeric, "String containing only letters is not numeric") 81 | XCTAssertFalse("1a1".isNumeric, "String containing letters and digits is not numeric") 82 | XCTAssertFalse("a1a".isNumeric, "String containing letters and digits is not numeric") 83 | } 84 | 85 | func testExtendedStringRangeConversions() { 86 | let uk = "🇬🇧" 87 | let str = "a😜b🇬🇧c" 88 | let r1 = str.range(of: uk)! 89 | 90 | let n1 = str.nsRange(from: r1) 91 | XCTAssertEqual((str as NSString).substring(with: n1), uk, "Range should have found the UK flag") 92 | 93 | let r2 = str.range(from: n1)! 94 | XCTAssertEqual(str.substring(with: r2), uk, "Range should have found the UK flag") 95 | } 96 | 97 | func testRepeatedRangeConversion() { 98 | var content: String = "This library was created by and can be found on " 99 | let regexStr = "" 100 | do { 101 | let regex = try NSRegularExpression(pattern: regexStr, options: .caseInsensitive) 102 | for match in regex.matches(in: content, options: [], range: NSRange(location: 0, length: content.characters.count)).reversed() { 103 | let reference = content.substring(with: content.range(from: match.rangeAt(1))!) 104 | content = content.replacingCharacters(in: content.range(from: match.range)!, with: "\(reference)") 105 | } 106 | XCTAssertEqual(content, "This library was created by ovenbits and can be found on github", "Repeated regex should have been handled") 107 | } catch { 108 | XCTFail("Regular expression creation issue") 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /AlexandriaTests/URLTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLTests.swift 3 | // 4 | // Created by Jonathan Landon on 10/26/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import XCTest 29 | import Alexandria 30 | 31 | class URLTests: XCTestCase { 32 | 33 | func testStringLiteralInitializer() { 34 | let url: URL = "https://github.com/ovenbits" 35 | 36 | XCTAssert(url.absoluteString == "https://github.com/ovenbits", "Absolute strings are not equal") 37 | XCTAssert(url.scheme == "https", "Scheme are not equal") 38 | XCTAssert(url.host == "github.com", "Hosts are not equal") 39 | XCTAssert(url.path == "/ovenbits", "Paths are not equal") 40 | } 41 | 42 | func testPathAppendingOperator() { 43 | var url: URL = "https://github.com" 44 | url = url + "ovenbits" 45 | url = url + "Alexandria" 46 | 47 | XCTAssert(url.path == "/ovenbits/Alexandria", "Paths are not equal") 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 Oven Bits, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Alexandria](https://raw.githubusercontent.com/ovenbits/Alexandria/assets/logo.png) 2 | 3 | 4 | # Alexandria 5 | 6 | [![Build Status](https://api.travis-ci.org/ovenbits/Alexandria.svg)](https://travis-ci.org/ovenbits/Alexandria) 7 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 8 | ![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Alexandria.svg?style=flat) 9 | ![Platform](https://img.shields.io/cocoapods/p/Alexandria.svg?style=flat) 10 | ![License](https://img.shields.io/badge/license-MIT-blue.svg) 11 | [![Twitter](https://img.shields.io/badge/twitter-@OvenBitsMobile-blue.svg?style=social)](http://twitter.com/OvenBitsMobile) 12 | 13 | A library of Swift extensions to turbocharge your iOS development. 14 | 15 | ## Background 16 | 17 | Here at [Oven Bits](http://ovenbits.com), we love Swift. We started using it when it was first released in 2014, and have been building large portions (or all) of our iOS applications in it since then. 18 | 19 | One of our favorite features in Swift is [extensions](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Extensions.html), and we've been using them heavily to add conveniences to UIKit & Foundation, and help "make it our own". 20 | 21 | _Alexandria_ (as in the [Library of Alexandria](https://en.wikipedia.org/wiki/Library_of_Alexandria)) is the result of that work. We use it as our "standard library" when starting iOS applications, and find it greatly speeds up our development time, and helps us get our applications off the ground quicker. Hopefully you will too! 22 | 23 | ## Requirements 24 | 25 | - iOS 8+ 26 | - Xcode 8+ 27 | - Swift 3 28 | 29 | If you need support for older versions of Swift, there are (semi-maintained) version-specific branches available. Our plan is to move forward as the Swift language progresses, but we understand some transition time is necessary. We will maintain the older version branches as long as we can, but most progress will occur on `master` moving forward with the language. 30 | 31 | 32 | ## Documentation 33 | 34 | Documentation is [available online](https://ovenbits.github.io/Alexandria). 35 | 36 | > [https://ovenbits.github.io/alexandria](https://ovenbits.github.io/Alexandria) 37 | 38 | ## Installation 39 | 40 | Pick your poison: [Carthage](https://github.com/carthage/carthage), [CocoaPods](https://github.com/cocoapods/cocoapods), or git submodules. 41 | 42 | ### Carthage 43 | 44 | Add the following to your `Cartfile`. 45 | 46 | ```ogdl 47 | github "ovenbits/Alexandria" 48 | ``` 49 | 50 | Then, run `carthage update`, and add the resulting framework from 51 | `Carthage/Build` into your project's "Linked Frameworks and Libraries" setting 52 | within Xcode. 53 | 54 | ### CocoaPods 55 | 56 | To add Alexandria, add the following to your `Podfile`. 57 | 58 | ```ruby 59 | use_frameworks! 60 | pod 'Alexandria' 61 | ``` 62 | 63 | Alternativly, you can install specific components: 64 | 65 | ```ruby 66 | pod 'Alexandria/Core' 67 | pod 'Alexandria/StoreKit' 68 | pod 'Alexandria/ImageEffects' 69 | ``` 70 | 71 | Then, run `pod install`. 72 | 73 | ### Submodules 74 | 75 | ```bash 76 | $ git submodule add git@github.com:ovenbits/Alexandria.git 77 | $ git submodule init 78 | $ git submodule update 79 | ``` 80 | 81 | Then, add the files manually to your Xcode project/workspace. 82 | 83 | ## Credits 84 | 85 | Alexandria is maintained by the team at [Oven Bits](https://ovenbits.com), a software design and development agency based in Dallas, Texas. 86 | 87 | Find us online: 88 | 89 | - [Twitter (@OvenBitsMobile)](https://twitter.com/OvenBitsMobile) 90 | 91 | Baked with love by Oven Bits, a software design and development agency based in Dallas, Texas. 92 | 93 | 94 | ## License 95 | 96 | Alexandria is released under the MIT license. See LICENSE for details. 97 | -------------------------------------------------------------------------------- /Sources/Array+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Extensions.swift 3 | // 4 | // Created by Ben Kreeger on 5/19/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | extension Array { 31 | /** 32 | Returns an Array containing the results of mapping transform over self. The transform provides not only 33 | each element of the array but also the index of tha item in the array. 34 | 35 | ``` 36 | let items: [SomeObject] = existingArray.mapWithIndex { index, response in 37 | return SomeObject(index: index, description: response.body) 38 | } 39 | ``` 40 | 41 | - parameter f: The transform that given an element of the array and an index returns an element of type T 42 | 43 | - returns: The array created by applying the transform to this array. 44 | */ 45 | public func mapWithIndex (_ f: (Int, Element) -> T) -> [T] { 46 | return zip(startIndex ..< endIndex, self).map(f) 47 | } 48 | 49 | /** 50 | Rotates an array by the number of places indicated by `shift`. 51 | 52 | This is useful for infinitely scrolling visuals, but where the data backing those visuals is finite. 53 | 54 | ``` 55 | var array = [1, 2, 3, 4, 5] 56 | 57 | let x = array.rotated(by: 2) // x should be [3, 4, 5, 1, 2] 58 | let y = array.rotated(by: -2) // y should be [4, 5, 1, 2, 3] 59 | ``` 60 | 61 | - parameter shift: The number of indices by which the array should be shifted. Positive shifts right, negative shifts left. 62 | 63 | - returns: Returns the rotated array. 64 | */ 65 | public func rotated(by shift: Int) -> Array { 66 | guard !isEmpty else { return [] } 67 | 68 | var array = self 69 | if shift > 0 { 70 | for _ in 1...shift { 71 | array.append(array.removeFirst()) 72 | } 73 | } else if shift < 0 { 74 | for _ in 1...abs(shift) { 75 | array.insert(array.removeLast(), at: 0) 76 | } 77 | } 78 | return array 79 | } 80 | 81 | /// Rotates self in-place. 82 | public mutating func rotate(by shift: Int) { 83 | self = rotated(by: shift) 84 | } 85 | } 86 | 87 | extension Array where Element: Equatable { 88 | /** 89 | Removes and returns the specified element from the array 90 | 91 | - parameter element: The element to remove from the array 92 | - returns: The removed element or nil, if the element was not found 93 | */ 94 | public mutating func remove(_ element: Element) -> Element? { 95 | guard let index = index(of: element) else { return nil } 96 | return remove(at: index) 97 | } 98 | 99 | 100 | /** 101 | Returns the element before the specified element. 102 | 103 | ``` 104 | let foo = [1,2,3] 105 | let two = foo.before(3) 106 | let one = foo.before(2) 107 | let nope = foo.before(1) 108 | ``` 109 | 110 | - parameter element: The element to index against. 111 | - returns: The element before the index element, or nil if there isn't one. 112 | */ 113 | public func before(_ element: Element) -> Element? { 114 | guard !isEmpty else { return nil } 115 | 116 | if let index = index(of: element) { 117 | return at(self.index(before: index)) 118 | } 119 | return nil 120 | } 121 | 122 | 123 | /** 124 | Returns the element after the specified element. 125 | 126 | ``` 127 | let foo = [1,2,3] 128 | let twp = foo.after(1) 129 | let three = foo.after(2) 130 | let nope = foo.after(3) 131 | ``` 132 | 133 | - parameter element: The element to index against. 134 | - returns: The element after the index element, or nil if there isn't one. 135 | */ 136 | public func after(_ element: Element) -> Element? { 137 | guard !isEmpty else { return nil } 138 | 139 | if let index = index(of: element) { 140 | return at(self.index(after: index)) 141 | } 142 | return nil 143 | } 144 | 145 | } 146 | 147 | extension Array where Element: Hashable { 148 | 149 | /** 150 | Returns an array containing no duplicates. 151 | 152 | #### Performance analysis: 153 | 154 | Three implementation were explored: 155 | 156 | **Set** 157 | ``` 158 | public func unique() -> [Element] { 159 | return Array(Set(self)) 160 | } 161 | ``` 162 | **Reduce** 163 | ``` 164 | public func unique() -> [Element] { 165 | return reduce([]) { !$0.contains($1) ? $0 + [$1] : $0 } 166 | } 167 | ``` 168 | **For-Loop** 169 | ``` 170 | public func unique() -> [Element] { 171 | var array: [Element] = [] 172 | for element in self where !array.contains(element) { 173 | array.append(element) 174 | } 175 | return array 176 | } 177 | ``` 178 | 179 | Performance tests were run on each implementation to determine which performed best. 180 | Starting with a 20 element array, we ran `unique()` 10,000 times and measured 181 | the execution time. This test was run 5 times. The average execution times are shown below. 182 | 183 | Set : 1.082s 184 | 185 | Reduce : 1.75s 186 | 187 | For-Loop : 0.952s 188 | 189 | The performance tests showed that the For-Loop implementation was the most performant. Reduce performed 190 | poorly, and while the Set implementation achieved similar performance to the For-Loop implementation, the 191 | order of the elements in the array is not preserved when using a Set. 192 | 193 | \* Tests were run using Xcode 7.2 and Swift 2.1. 194 | */ 195 | public func unique() -> [Element] { 196 | 197 | var array: [Element] = [] 198 | 199 | for element in self where !array.contains(element) { 200 | array.append(element) 201 | } 202 | 203 | return array 204 | } 205 | 206 | /// Removes duplicates from self in-place. 207 | public mutating func formUnique() { 208 | self = unique() 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Sources/CALayer+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 4/15/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import UIKit 29 | 30 | private final class AnimationDelegate: NSObject, CAAnimationDelegate { 31 | 32 | private var completion: (Bool) -> Void 33 | 34 | init(completion: @escaping (Bool) -> Void) { 35 | self.completion = completion 36 | } 37 | 38 | fileprivate func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 39 | completion(flag) 40 | } 41 | } 42 | 43 | extension CALayer { 44 | 45 | /** 46 | Add the specified animation object to the layer's render tree with a completion closure. 47 | 48 | - parameter animation: The animation to be added to the render tree. 49 | - parameter key: A string that identifier the animation. 50 | - parameter completion: A closure that is executed upon completion of the animation. 51 | */ 52 | public func add(_ animation: CAAnimation, forKey key: String?, withCompletion completion: @escaping (Bool) -> Void) { 53 | animation.delegate = AnimationDelegate(completion: completion) 54 | add(animation, forKey: key) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/CAMediaTimingFunction+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CAMediaTimingFunction+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 12/6/15. 5 | // 6 | // Inspired by: http://bandes-stor.ch/blog/2015/11/28/help-yourself-to-some-swift/ 7 | // 8 | // The static timing function properties are be initialized lazily the first time 9 | // they're accessed. The @nonobjc is required to prevent the compiler from trying 10 | // to generate a dynamic accessor for a static (therefore final) property. 11 | // 12 | // The MIT License (MIT) 13 | // 14 | // Copyright (c) 2014-2016 Oven Bits, LLC 15 | // 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to deal 18 | // in the Software without restriction, including without limitation the rights 19 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | // copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // 23 | // The above copyright notice and this permission notice shall be included in all 24 | // copies or substantial portions of the Software. 25 | // 26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | // SOFTWARE. 33 | 34 | import UIKit 35 | 36 | extension CAMediaTimingFunction { 37 | 38 | /// A media timing function for ease-in 39 | @nonobjc public static let easeIn = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) 40 | 41 | /// A media timing function for ease-in and ease-out 42 | @nonobjc public static let easeInEaseOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 43 | 44 | /// A media timing function for ease-out. 45 | @nonobjc public static let easeOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) 46 | 47 | /// A linear media timing function. 48 | @nonobjc public static let linear = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) 49 | 50 | /** 51 | Create a CAMediaTimingFunction from the specified control points. 52 | 53 | - parameter x1: The x coordinate of the first control point. 54 | - parameter y1: The y coordinate of the first control point. 55 | - parameter x2: The x coordinate of the second control point. 56 | - parameter y2: The y coordinate of the second control point. 57 | 58 | - returns: A CAMediaTimingFunction with the specified control points. 59 | */ 60 | public static func controlPoints(_ x1: Float, _ y1: Float, _ x2: Float, _ y2: Float) -> CAMediaTimingFunction { 61 | return .init(controlPoints: x1, y1, x2, y2) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/CGAffineTransform+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGAffineTransform+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 12/14/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | 29 | import UIKit 30 | 31 | extension CGAffineTransform { 32 | 33 | // MARK: Scale 34 | 35 | /** 36 | Returns a transform which scales by `(sx, sy)`. Equivalent to `CGAffineTransformMakeScale(sx, sy)`. 37 | 38 | - parameter sx: The `x` scale. 39 | - parameter sy: The `y` scale. 40 | 41 | - returns: A transform scaled by `(sx, sy)`. 42 | */ 43 | public static func scale(_ sx: CGFloat, _ sy: CGFloat) -> CGAffineTransform { 44 | return CGAffineTransform(scaleX: sx, y: sy) 45 | } 46 | 47 | // MARK: Translate 48 | 49 | /** 50 | Returns a transform which translates by `(tx, ty)`. Equivalent to `CGAffineTransformMakeTranslation(tx, ty)`. 51 | 52 | - parameter tx: The `x` translation. 53 | - parameter ty: The `y` translation. 54 | 55 | - returns: A transform translated by `(tx, ty)`. 56 | */ 57 | public static func translate(_ tx: CGFloat, _ ty: CGFloat) -> CGAffineTransform { 58 | return CGAffineTransform(translationX: tx, y: ty) 59 | } 60 | 61 | // MARK: Rotate 62 | 63 | /** 64 | Returns a transform which rotates by `angle` radians. Equivalent to `CGAffineTransformMakeRotation(angle)`. 65 | 66 | - parameter angle: The rotation angle in radians. 67 | 68 | - returns: A transform rotated by `angle` radians. 69 | */ 70 | public static func rotate(_ angle: CGFloat) -> CGAffineTransform { 71 | return CGAffineTransform(rotationAngle: angle) 72 | } 73 | 74 | // MARK: Invert 75 | 76 | /** 77 | Returns a transform which inverts another transform. Equivalent to `CGAffineTransformInvert(transform)`. 78 | 79 | - parameter transform: The transform to invert. 80 | 81 | - returns: An inverted transform. 82 | */ 83 | public static func invert(_ transform: CGAffineTransform) -> CGAffineTransform { 84 | return transform.inverted() 85 | } 86 | 87 | // MARK: Concat 88 | 89 | /** 90 | Returns the concatenation of an array of transforms. 91 | 92 | - parameter transforms: The array of transforms to concatenate. 93 | 94 | - returns: A concatenation of the array of transforms. 95 | */ 96 | public static func concat(_ transforms: CGAffineTransform...) -> CGAffineTransform { 97 | var transform: CGAffineTransform = .identity 98 | transforms.forEach { transform = transform.concatenating($0) } 99 | 100 | return transform 101 | } 102 | 103 | /** 104 | Concatenates two CGAffineTransforms and returns the result. Equivalent to `CGAffineTransformConcat(lhs, rhs)`. 105 | 106 | - parameter lhs: The first transform. 107 | - parameter rhs: The second transform. 108 | 109 | - returns: The concatentation of the two transforms. 110 | */ 111 | public static func *(lhs: CGAffineTransform, rhs: CGAffineTransform) -> CGAffineTransform { 112 | return .concat(lhs, rhs) 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /Sources/CGFloat+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGFloat+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 2/9/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | import CoreGraphics 30 | 31 | extension CGFloat { 32 | /// Generate a random CGFloat bounded by a closed interval range. 33 | public static func random(_ range: ClosedRange) -> CGFloat { 34 | return CGFloat(arc4random()) / CGFloat(UInt64(UINT32_MAX)) * (range.upperBound - range.lowerBound) + range.lowerBound 35 | } 36 | 37 | /// Generate a random CGFloat bounded by a range from min to max. 38 | public static func random(min: CGFloat, max: CGFloat) -> CGFloat { 39 | return random(min...max) 40 | } 41 | 42 | /** 43 | Round self to a specified number of decimal places. 44 | 45 | - parameter places: The number of decimal places by which to round self. 46 | - returns: A CGFloat value, rounded to the specified number of decimal places. 47 | 48 | Examples: 49 | ``` 50 | let val: CGFloat = 12.345678 51 | 52 | val.rounded(places: 2) // 12.35 53 | val.rounded(places: 3) // 12.346 54 | ``` 55 | */ 56 | public func rounded(places: UInt) -> CGFloat { 57 | let multiplier = pow(10, CGFloat(places)) 58 | return (self * multiplier).rounded() / multiplier 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/CharacterSet+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacterSet+Extensions.swift 3 | // 4 | // Created by hsoi on 11/17/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | 29 | import Foundation 30 | 31 | /** 32 | Extensions to CharacterSet 33 | */ 34 | public extension CharacterSet { 35 | 36 | /** 37 | 38 | Returns an `CharacterSet` comprised of the Unicode characters for currency symbols, as specified in: 39 | 40 | http://unicode.org/charts/PDF/U20A0.pdf 41 | 42 | which, at the time of this writing, is based upon The Unicode Standard version 8.0. 43 | 44 | According to that specification, the currency symbols fall in the range: 20A0–20CF, along with a handful of other 45 | symbols in other blocks. 46 | 47 | 48 | - returns: An CharacterSet of currency symbols 49 | 50 | */ 51 | public static var currencySymbols: CharacterSet { 52 | let charset = NSMutableCharacterSet.currencySymbols() 53 | return charset.copy() as! CharacterSet 54 | } 55 | } 56 | 57 | extension CharacterSet: ExpressibleByStringLiteral { 58 | 59 | /// Creates a CharacterSet initialized to the given string value. 60 | public init(stringLiteral value: StringLiteralType) { 61 | self.init(charactersIn: value) 62 | } 63 | 64 | /// Creates a CharacterSet initialized to the given value. 65 | public init(extendedGraphemeClusterLiteral value: StringLiteralType) { 66 | self.init(charactersIn: value) 67 | } 68 | 69 | /// Creates a CharacterSet initialized to the given value. 70 | public init(unicodeScalarLiteral value: StringLiteralType) { 71 | self.init(charactersIn: value) 72 | } 73 | } 74 | 75 | 76 | /** 77 | Extensions to NSMutableCharacterSet 78 | */ 79 | public extension NSMutableCharacterSet { 80 | 81 | /** 82 | 83 | Returns an `NSMutableCharacterSet` comprised of the Unicode characters for currency symbols, as specified in: 84 | 85 | http://unicode.org/charts/PDF/U20A0.pdf 86 | 87 | which, at the time of this writing, is based upon The Unicode Standard version 8.0. 88 | 89 | According to that specification, the currency symbols fall in the range: 20A0–20CF, along with a handful of other 90 | symbols in other blocks. 91 | 92 | 93 | - returns: An NSMutableCharacterSet of currency symbols 94 | 95 | */ 96 | public class func currencySymbols() -> NSMutableCharacterSet { 97 | let charset = NSMutableCharacterSet() 98 | 99 | // http://unicode.org/charts/PDF/U20A0.pdf 100 | // http://www.alanwood.net/unicode/currency_symbols.html 101 | let otherSymbols = "\u{0024}\u{00A2}\u{00A3}\u{00A4}\u{00A5}\u{0192}\u{058F}\u{060B}\u{09F2}\u{09F3}\u{0AF1}\u{0BF9}\u{0E3F}\u{17DB}\u{2133}\u{3350}\u{5143}\u{5186}\u{5706}\u{570E}\u{5713}\u{571C}\u{A838}\u{C6D0}\u{FDFC}\u{FF04}\u{FFE0}\u{FFE1}\u{FFE5}\u{FFE6}\u{1F4B2}" 102 | charset.addCharacters(in: otherSymbols) 103 | 104 | let range = NSRange(location: 0x20A0, length: 0x20CF - 0x20A0 + 1) 105 | charset.addCharacters(in: range) 106 | 107 | return charset 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Sources/Collection+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionType+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 9/22/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | extension Collection where Self.Index == Self.Indices.Iterator.Element { 31 | /** 32 | Returns an optional element. If the `index` does not exist in the collection, the subscript returns nil. 33 | 34 | - parameter safe: The index of the element to return, if it exists. 35 | 36 | - returns: An optional element from the collection at the specified index. 37 | */ 38 | public subscript(safe i: Index) -> Self.Iterator.Element? { 39 | return at(i) 40 | } 41 | 42 | /** 43 | Returns an optional element. If the `index` does not exist in the collection, the function returns nil. 44 | 45 | - parameter index: The index of the element to return, if it exists. 46 | 47 | - returns: An optional element from the collection at the specified index. 48 | */ 49 | public func at(_ i: Index) -> Self.Iterator.Element? { 50 | return indices.contains(i) ? self[i] : nil 51 | } 52 | } 53 | 54 | public extension Collection { 55 | 56 | /** 57 | Creats a shuffled version of this array using the Fisher-Yates (fast and uniform) shuffle. 58 | Non-mutating. From http://stackoverflow.com/a/24029847/194869 59 | 60 | - returns: A shuffled version of this array. 61 | */ 62 | public func shuffled() -> [Generator.Element] { 63 | var list = Array(self) 64 | list.shuffle() 65 | return list 66 | } 67 | } 68 | 69 | public extension MutableCollection where Index == Int, IndexDistance == Int { 70 | /** 71 | Shuffle the array using the Fisher-Yates (fast and uniform) shuffle. Mutating. 72 | From http://stackoverflow.com/a/24029847/194869 73 | */ 74 | public mutating func shuffle() { 75 | // Empty and single-element collections don't shuffle. 76 | guard count > 1 else { return } 77 | 78 | for i in 0 ..< (count - 1) { 79 | let j = Int(arc4random_uniform(UInt32(count - i))) + i 80 | guard i != j else { continue } 81 | swap(&self[i], &self[j]) 82 | } 83 | } 84 | 85 | /** 86 | Returns a random element from the collection. 87 | 88 | - returns: A random element from the collection. 89 | */ 90 | public func random() -> Generator.Element { 91 | let index = Int(arc4random_uniform(UInt32(count))) 92 | return self[index] 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/Comparable+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Comparable+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 12/1/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | extension Comparable { 29 | 30 | /** 31 | Bound the current value between a minimum and maximum value 32 | 33 | - parameter min: The minimum possible value 34 | - parameter max: The maximum possible value 35 | 36 | - returns: The current value bounded between a minimum and maximum value 37 | */ 38 | public func limited(min: Self, max: Self) -> Self { 39 | var value = self 40 | value.limit(min: min, max: max) 41 | return value 42 | } 43 | 44 | /** 45 | Bound the current value between a minimum and maximum value 46 | 47 | - parameter min: The minimum possible value 48 | - parameter max: The maximum possible value 49 | 50 | - returns: The current value bounded between a minimum and maximum value 51 | */ 52 | public func limited(_ min: Self, _ max: Self) -> Self { 53 | return limited(min: min, max: max) 54 | } 55 | 56 | /** 57 | Bound self between a minimum and maximum value, in place 58 | 59 | - parameter min: The minimum possible value 60 | - parameter max: The maximum possible value 61 | */ 62 | public mutating func limit(min: Self, max: Self) { 63 | self = Swift.max(Swift.min(self, max), min) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/CoreGraphics+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreGraphics+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 12/6/15. 5 | // 6 | // Inspired by: http://bandes-stor.ch/blog/2015/11/28/help-yourself-to-some-swift/ 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Copyright (c) 2014-2016 Oven Bits, LLC 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in all 20 | // copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | // SOFTWARE. 29 | 30 | import UIKit 31 | 32 | // MARK: - CGContext 33 | 34 | extension CGContext { 35 | /// The current graphics context 36 | public static var current: CGContext? { 37 | return UIGraphicsGetCurrentContext() 38 | } 39 | } 40 | 41 | // MARK: - CGRect 42 | 43 | extension CGRect { 44 | /** 45 | Returns a CGRect value initialized with an origin at (0,0) and the provided width and height 46 | 47 | - parameter width: The width 48 | - parameter height: The height 49 | 50 | - returns: A CGRect value initialized with an origin at (0,0) and the provided width and height 51 | */ 52 | public init(width: CGFloat, height: CGFloat) { 53 | self.init(x: 0, y: 0, width: width, height: height) 54 | } 55 | } 56 | 57 | // MARK: - CGPoint 58 | 59 | extension CGPoint { 60 | /** 61 | Returns the distance between two points. 62 | 63 | - parameter point: The point to which to calculate the distance. 64 | 65 | - returns: The distance between self and another point. 66 | */ 67 | public func distance(to point: CGPoint) -> CGFloat { 68 | return sqrt(pow(point.x - x, 2) + pow(point.y - y, 2)) 69 | } 70 | } 71 | 72 | public func +(lhs: CGPoint, rhs: CGPoint) -> CGPoint { 73 | return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 74 | } 75 | 76 | public func +=(lhs: inout CGPoint, rhs: CGPoint) { 77 | lhs = lhs + rhs 78 | } 79 | 80 | public func -(lhs: CGPoint, rhs: CGPoint) -> CGPoint { 81 | return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) 82 | } 83 | 84 | public func -=(lhs: inout CGPoint, rhs: CGPoint) { 85 | lhs = lhs - rhs 86 | } 87 | 88 | public func *(lhs: CGPoint, rhs: CGPoint) -> CGPoint { 89 | return CGPoint(x: lhs.x * rhs.x, y: lhs.y * rhs.y) 90 | } 91 | 92 | public func *=(lhs: inout CGPoint, rhs: CGPoint) { 93 | lhs = lhs * rhs 94 | } 95 | 96 | public func /(lhs: CGPoint, rhs: CGPoint) -> CGPoint { 97 | return CGPoint(x: lhs.x / rhs.x, y: lhs.y / rhs.y) 98 | } 99 | 100 | public func /=(lhs: inout CGPoint, rhs: CGPoint) { 101 | lhs = lhs / rhs 102 | } 103 | -------------------------------------------------------------------------------- /Sources/Date+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 4/15/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | extension Date { 31 | 32 | /** 33 | TimeFormat is an enum for formatting timestamps. 34 | 35 | Given an hour, minute, and second, the time will be formatted in one of four formats. 36 | - `hourMinuteMilitary` (e.g. "19:41") 37 | - `hourMinutePeriod` (e.g. "07:41 PM") 38 | - `hourMinuteSecondMilitary` (e.g. "19:41:30") 39 | - `hourMinuteSecondPeriod` (e.g. "07:41:30 PM") 40 | */ 41 | public enum TimeFormat { 42 | /// Military time with hours and minutes (e.g. "19:41") 43 | case hourMinuteMilitary 44 | /// Standard time with hours and minutes (e.g. "07:41 PM") 45 | case hourMinutePeriod 46 | /// Military time with hours, minutes, and seconds (e.g. "19:41:30") 47 | case hourMinuteSecondMilitary 48 | /// Standard time with hours, minutes, and seconds (e.g. "07:41:30 PM") 49 | case hourMinuteSecondPeriod 50 | 51 | /// Time period (i.e. `AM` and `PM`) 52 | private enum Period: String { 53 | case am 54 | case pm 55 | } 56 | 57 | /** 58 | Convert the hour, minute, and second to the specified format. 59 | 60 | - parameter hour: The hour. 61 | - parameter minute: The minute. 62 | - parameter second: The second. 63 | 64 | - returns: The formatted string 65 | */ 66 | public func stringify(hour: Int, minute: Int, second: Int) -> String { 67 | 68 | assert((0..<24).contains(hour), "The hour \(hour) is outside the valid range of 0-23") 69 | assert((0..<60).contains(minute), "The minute \(minute) is outside the valid range of 0-59") 70 | assert((0..<60).contains(second), "The second \(second) is outside the valid range of 0-59") 71 | 72 | switch self { 73 | case .hourMinuteMilitary: 74 | return String(format: "%02d:%02d", hour, minute) 75 | case .hourMinutePeriod: 76 | let (adjustedHour, period) = adjustHourForPeriod(hour) 77 | return String(format: "%02d:%02d %@", adjustedHour, minute, period.rawValue.uppercased()) 78 | case .hourMinuteSecondMilitary: 79 | return String(format: "%02d:%02d:%02d", hour, minute, second) 80 | case .hourMinuteSecondPeriod: 81 | let (adjustedHour, period) = adjustHourForPeriod(hour) 82 | return String(format: "%02d:%02d:%02d %@", adjustedHour, minute, second, period.rawValue.uppercased()) 83 | } 84 | } 85 | 86 | /** 87 | Adjust the hour to a 12-hour time and return the period. 88 | 89 | For example, and input of 13 returns (hour: 1, period: .PM). 90 | 91 | - parameter hour: The hour to adjust. 92 | 93 | - returns: The adjusted hour and period 94 | */ 95 | private func adjustHourForPeriod(_ hour: Int) -> (hour: Int, period: Period) { 96 | let adjustedHour: Int 97 | let period: Period 98 | 99 | switch hour { 100 | case 0: 101 | adjustedHour = 12 102 | period = .am 103 | case 12: 104 | adjustedHour = 12 105 | period = .pm 106 | case 13...23: 107 | adjustedHour = hour - 12 108 | period = .pm 109 | default: 110 | adjustedHour = hour 111 | period = .am 112 | } 113 | 114 | return (hour: adjustedHour, period: period) 115 | } 116 | } 117 | 118 | /** 119 | Returns a Date value initialized with the provided month, day, and year 120 | 121 | - parameter month: The month 122 | - parameter day: The day 123 | - parameter year: The year 124 | 125 | - returns: A Date value initialized with the provided month, day, and year 126 | */ 127 | public init?(month: Int, day: Int, year: Int) { 128 | let calendar = Calendar.autoupdatingCurrent 129 | let components = DateComponents(year: year, month: month, day: day) 130 | 131 | if let date = calendar.date(from: components) { 132 | self.init(timeInterval: 0, since: date) 133 | } 134 | else { 135 | self.init(timeIntervalSince1970: 0) // required by the compiler 136 | return nil 137 | } 138 | } 139 | 140 | /// The current year 141 | @available(*, deprecated: 2.0, message: "use Date().year") 142 | public static var currentYear: Int { 143 | return Date().year 144 | } 145 | 146 | /// The current month 147 | @available(*, deprecated: 2.0, message: "use Date().month") 148 | public static var currentMonth: Int { 149 | return Date().month 150 | } 151 | 152 | /// The current day 153 | @available(*, deprecated: 2.0, message: "use Date().day") 154 | public static var currentDay: Int { 155 | return Date().day 156 | } 157 | 158 | /// The current hour 159 | @available(*, deprecated: 2.0, message: "use Date().hour") 160 | public static var currentHour: Int { 161 | return Date().hour 162 | } 163 | 164 | /// The current minute 165 | @available(*, deprecated: 2.0, message: "use Date().minute") 166 | public static var currentMinute: Int { 167 | return Date().minute 168 | } 169 | 170 | /// The current second 171 | @available(*, deprecated: 2.0, message: "use Date().second") 172 | public static var currentSecond: Int { 173 | return Date().second 174 | } 175 | 176 | /// The era component of self 177 | public var era: Int { 178 | return components.era ?? 0 179 | } 180 | 181 | /// The year component of self 182 | public var year: Int { 183 | return components.year ?? 0 184 | } 185 | 186 | /// The month component of self 187 | public var month: Int { 188 | return components.month ?? 0 189 | } 190 | 191 | /// The day component of self 192 | public var day: Int { 193 | return components.day ?? 0 194 | } 195 | 196 | /// The hour component of self 197 | public var hour: Int { 198 | return components.hour ?? 0 199 | } 200 | 201 | /// The minute component of self 202 | public var minute: Int { 203 | return components.minute ?? 0 204 | } 205 | 206 | /// The second component of self 207 | public var second: Int { 208 | return components.second ?? 0 209 | } 210 | 211 | /// The weekday component of self 212 | public var weekday: Int { 213 | return components.weekday ?? 0 214 | } 215 | 216 | /// The weekdayOrdinal component of self 217 | public var weekdayOrdinal: Int { 218 | return components.weekdayOrdinal ?? 0 219 | } 220 | 221 | /// The quarter component of self 222 | public var quarter: Int { 223 | return components.quarter ?? 0 224 | } 225 | 226 | /// The weekOfMonth component of self 227 | public var weekOfMonth: Int { 228 | return components.weekOfMonth ?? 0 229 | } 230 | 231 | /// The weekOfYear component of self 232 | public var weekOfYear: Int { 233 | return components.weekOfYear ?? 0 234 | } 235 | 236 | /// The yearForWeekOfYear component of self 237 | public var yearForWeekOfYear: Int { 238 | return components.yearForWeekOfYear ?? 0 239 | } 240 | 241 | /// The nanosecond component of self 242 | public var nanosecond: Int { 243 | return components.nanosecond ?? 0 244 | } 245 | 246 | /// The calendar component of self 247 | public var calendar: Calendar? { 248 | return components.calendar 249 | } 250 | 251 | /// The timeZone component of self 252 | public var timeZone: TimeZone? { 253 | return components.timeZone 254 | } 255 | 256 | /** 257 | The time, formatted with a specified TimeFormat. 258 | 259 | Format options: 260 | - `HourMinuteMilitary` (e.g. "19:41") 261 | - `HourMinutePeriod` (e.g. "07:41 PM") 262 | - `HourMinuteSecondMilitary` (e.g. "19:41:30") 263 | - `HourMinuteSecondPeriod` (e.g. "07:41:30 PM") 264 | 265 | Default format is `HourMinuteMilitary`. 266 | 267 | - parameter format: The format to use for the time (optional). 268 | 269 | - returns: The formatted time string. 270 | */ 271 | public func time(format: TimeFormat = .hourMinuteMilitary) -> String { 272 | return format.stringify(hour: hour, minute: minute, second: second) 273 | } 274 | } 275 | 276 | // MARK: Private 277 | 278 | extension Date { 279 | 280 | fileprivate var allCalendarComponents: Set { 281 | return [ 282 | .era, 283 | .year, 284 | .month, 285 | .day, 286 | .hour, 287 | .minute, 288 | .second, 289 | .weekday, 290 | .weekdayOrdinal, 291 | .quarter, 292 | .weekOfMonth, 293 | .weekOfYear, 294 | .yearForWeekOfYear, 295 | .nanosecond, 296 | .calendar, 297 | .timeZone 298 | ] 299 | } 300 | 301 | fileprivate var components: DateComponents { 302 | return Calendar.autoupdatingCurrent.dateComponents(allCalendarComponents, from: self) 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /Sources/Dictionary+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 11/19/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | extension Dictionary { 31 | /** 32 | Add dictionary to self in-place. 33 | 34 | - parameter dictionary: The dictionary to add to self 35 | */ 36 | public mutating func formUnion(_ dictionary: Dictionary) { 37 | dictionary.forEach { updateValue($0.1, forKey: $0.0) } 38 | } 39 | 40 | /** 41 | Return a dictionary containing the union of two dictionaries 42 | 43 | - parameter dictionary: The dictionary to add 44 | - returns: The combination of self and dictionary 45 | */ 46 | public func union(_ dictionary: Dictionary) -> Dictionary { 47 | var completeDictionary = self 48 | completeDictionary.formUnion(dictionary) 49 | return completeDictionary 50 | } 51 | } 52 | 53 | /** 54 | Combine the contents of dictionaries into a single dictionary. Equivalent to `lhs.union(rhs)`. 55 | 56 | - parameter lhs: The first dictionary. 57 | - parameter rhs: The second dictionary. 58 | - returns: The combination of the two input dictionaries 59 | */ 60 | public func +(lhs: Dictionary, rhs: Dictionary) -> Dictionary { 61 | return lhs.union(rhs) 62 | } 63 | 64 | /** 65 | Add the contents of one dictionary to another dictionary. Equivalent to `lhs.unionInPlace(rhs)`. 66 | 67 | - parameter lhs: The dictionary in which to add key-values. 68 | - parameter rhs: The dictionary from which to add key-values. 69 | */ 70 | public func +=(lhs: inout Dictionary, rhs: Dictionary) { 71 | lhs.formUnion(rhs) 72 | } 73 | -------------------------------------------------------------------------------- /Sources/Double+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Double+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 12/6/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | extension Double { 29 | /// Generate a random Double bounded by a closed interval range. 30 | public static func random(_ range: ClosedRange) -> Double { 31 | return Double(arc4random()) / Double(UInt64(UINT32_MAX)) * (range.upperBound - range.lowerBound) + range.lowerBound 32 | } 33 | 34 | /// Generate a random Double bounded by a range from min to max. 35 | public static func random(min: Double, max: Double) -> Double { 36 | return random(min...max) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Float+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Float+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 2/9/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | extension Float { 29 | /// Generate a random Float bounded by a closed interval range. 30 | public static func random(_ range: ClosedRange) -> Float { 31 | return Float(arc4random()) / Float(UInt64(UINT32_MAX)) * (range.upperBound - range.lowerBound) + range.lowerBound 32 | } 33 | 34 | /// Generate a random Float bounded by a range from min to max. 35 | public static func random(min: Float, max: Float) -> Float { 36 | return random(min...max) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ImageEffects/UIImage+Effects.swift: -------------------------------------------------------------------------------- 1 | /* 2 | File: UIImage+ImageEffects.m 3 | Abstract: This is a category of UIImage that adds methods to apply blur and tint effects to an image. This is the code you’ll want to look out to find out how to use vImage to efficiently calculate a blur. 4 | Version: 1.0 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2013 Apple Inc. All Rights Reserved. 45 | 46 | 47 | Copyright © 2013 Apple Inc. All rights reserved. 48 | WWDC 2013 License 49 | 50 | NOTE: This Apple Software was supplied by Apple as part of a WWDC 2013 51 | Session. Please refer to the applicable WWDC 2013 Session for further 52 | information. 53 | 54 | IMPORTANT: This Apple software is supplied to you by Apple Inc. 55 | ("Apple") in consideration of your agreement to the following terms, and 56 | your use, installation, modification or redistribution of this Apple 57 | software constitutes acceptance of these terms. If you do not agree with 58 | these terms, please do not use, install, modify or redistribute this 59 | Apple software. 60 | 61 | In consideration of your agreement to abide by the following terms, and 62 | subject to these terms, Apple grants you a non-exclusive license, under 63 | Apple's copyrights in this original Apple software (the "Apple 64 | Software"), to use, reproduce, modify and redistribute the Apple 65 | Software, with or without modifications, in source and/or binary forms; 66 | provided that if you redistribute the Apple Software in its entirety and 67 | without modifications, you must retain this notice and the following 68 | text and disclaimers in all such redistributions of the Apple Software. 69 | Neither the name, trademarks, service marks or logos of Apple Inc. may 70 | be used to endorse or promote products derived from the Apple Software 71 | without specific prior written permission from Apple. Except as 72 | expressly stated in this notice, no other rights or licenses, express or 73 | implied, are granted by Apple herein, including but not limited to any 74 | patent rights that may be infringed by your derivative works or by other 75 | works in which the Apple Software may be incorporated. 76 | 77 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES 78 | NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE 79 | IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR 80 | A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 81 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 82 | 83 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 84 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 85 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 86 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 87 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 88 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 89 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 90 | POSSIBILITY OF SUCH DAMAGE. 91 | 92 | EA1002 93 | 5/3/2013 94 | */ 95 | 96 | // 97 | // UIImage.swift 98 | // Today 99 | // 100 | // Created by Alexey Globchastyy on 15/09/14. 101 | // Copyright (c) 2014 Alexey Globchastyy. All rights reserved. 102 | // 103 | 104 | import UIKit 105 | import Accelerate 106 | 107 | public extension UIImage { 108 | 109 | /** 110 | Applies a lightening (blur) effect to the image 111 | 112 | - returns: The lightened image, or nil if error. 113 | */ 114 | public func applyLightEffect() -> UIImage? { 115 | return applyBlur(withRadius: 30, tintColor: UIColor(white: 1.0, alpha: 0.3), saturationDeltaFactor: 1.0) 116 | } 117 | 118 | 119 | /** 120 | Applies an extra lightening (blur) effect to the image 121 | 122 | - returns: The extra lightened image, or nil if error. 123 | */ 124 | public func applyExtraLightEffect() -> UIImage? { 125 | return applyBlur(withRadius: 20, tintColor: UIColor(white: 0.97, alpha: 0.82), saturationDeltaFactor: 1.8) 126 | } 127 | 128 | 129 | /** 130 | Applies a darkening (blur) effect to the image. 131 | 132 | - returns: The darkened image, or nil if error. 133 | */ 134 | public func applyDarkEffect() -> UIImage? { 135 | return applyBlur(withRadius: 20, tintColor: UIColor(white: 0.11, alpha: 0.73), saturationDeltaFactor: 1.8) 136 | } 137 | 138 | 139 | /** 140 | Tints the image with the given color. 141 | 142 | - parameter tintColor: The tint color 143 | 144 | - returns: The tinted image, or nil if error. 145 | */ 146 | public func applyTintEffect(withColor tintColor: UIColor) -> UIImage? { 147 | let effectColorAlpha: CGFloat = 0.6 148 | var effectColor = tintColor 149 | 150 | let componentCount = tintColor.cgColor.numberOfComponents 151 | 152 | if componentCount == 2 { 153 | var b: CGFloat = 0 154 | if tintColor.getWhite(&b, alpha: nil) { 155 | effectColor = UIColor(white: b, alpha: effectColorAlpha) 156 | } 157 | } else { 158 | var red: CGFloat = 0 159 | var green: CGFloat = 0 160 | var blue: CGFloat = 0 161 | 162 | if tintColor.getRed(&red, green: &green, blue: &blue, alpha: nil) { 163 | effectColor = UIColor(red: red, green: green, blue: blue, alpha: effectColorAlpha) 164 | } 165 | } 166 | 167 | return applyBlur(withRadius: 10, tintColor: effectColor, saturationDeltaFactor: -1.0, maskImage: nil) 168 | } 169 | 170 | 171 | /** 172 | Core function to create a new image with the given blur. 173 | 174 | - parameter blurRadius: The blur radius 175 | - parameter tintColor: The color to tint the image; optional. 176 | - parameter saturationDeltaFactor: The delta by which to change the image saturation 177 | - parameter maskImage: An optional image mask. 178 | 179 | - returns: The adjusted image, or nil if error. 180 | */ 181 | public func applyBlur(withRadius blurRadius: CGFloat, tintColor: UIColor?, saturationDeltaFactor: CGFloat, maskImage: UIImage? = nil) -> UIImage? { 182 | // Check pre-conditions. 183 | 184 | guard size.width >= 1 && size.height >= 1 else { 185 | print("*** error: invalid size: \(size.width) x \(size.height). Both dimensions must be >= 1: \(self)") 186 | return nil 187 | } 188 | guard let cgImage = self.cgImage else { 189 | print("*** error: image must be backed by a CGImage: \(self)") 190 | return nil 191 | } 192 | if maskImage != nil && maskImage!.cgImage == nil { 193 | print("*** error: maskImage must be backed by a CGImage: \(maskImage)") 194 | return nil 195 | } 196 | 197 | defer { UIGraphicsEndImageContext() } 198 | 199 | let epsilon = CGFloat(FLT_EPSILON) 200 | let screenScale = UIScreen.main.scale 201 | let imageRect = CGRect(origin: .zero, size: size) 202 | var effectImage: UIImage? = self 203 | 204 | let hasBlur = blurRadius > epsilon 205 | let hasSaturationChange = fabs(saturationDeltaFactor - 1.0) > epsilon 206 | 207 | if hasBlur || hasSaturationChange { 208 | func createEffectBuffer(_ context: CGContext) -> vImage_Buffer { 209 | let data = context.data 210 | let width = context.width 211 | let height = context.height 212 | let rowBytes = context.bytesPerRow 213 | 214 | return vImage_Buffer(data: data, height: UInt(height), width: UInt(width), rowBytes: rowBytes) 215 | } 216 | 217 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 218 | 219 | guard let effectInContext = UIGraphicsGetCurrentContext() else { return self } 220 | 221 | effectInContext.scaleBy(x: 1, y: -1) 222 | effectInContext.translateBy(x: 0, y: -size.height) 223 | effectInContext.draw(cgImage, in: imageRect) 224 | 225 | var effectInBuffer = createEffectBuffer(effectInContext) 226 | 227 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 228 | 229 | guard let effectOutContext = UIGraphicsGetCurrentContext() else { return self } 230 | 231 | var effectOutBuffer = createEffectBuffer(effectOutContext) 232 | 233 | if hasBlur { 234 | // A description of how to compute the box kernel width from the Gaussian 235 | // radius (aka standard deviation) appears in the SVG spec: 236 | // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement 237 | // 238 | // For larger values of 's' (s >= 2.0), an approximation can be used: Three 239 | // successive box-blurs build a piece-wise quadratic convolution kernel, which 240 | // approximates the Gaussian kernel to within roughly 3%. 241 | // 242 | // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) 243 | // 244 | // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. 245 | // 246 | 247 | let inputRadius = blurRadius * screenScale 248 | let input = inputRadius * 3 * sqrt(2 * .pi) / 4 + 0.5 249 | var radius = UInt32(floor(input)) 250 | if radius % 2 != 1 { 251 | radius += 1 // force radius to be odd so that the three box-blur methodology works. 252 | } 253 | 254 | let imageEdgeExtendFlags = vImage_Flags(kvImageEdgeExtend) 255 | 256 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 257 | vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 258 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 259 | } 260 | 261 | var effectImageBuffersAreSwapped = false 262 | 263 | if hasSaturationChange { 264 | let s: CGFloat = saturationDeltaFactor 265 | let floatingPointSaturationMatrix: [CGFloat] = [ 266 | 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0, 267 | 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0, 268 | 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0, 269 | 0, 0, 0, 1 270 | ] 271 | 272 | let divisor: CGFloat = 256 273 | let saturationMatrix = floatingPointSaturationMatrix.map { Int16(round($0) * divisor) } 274 | 275 | if hasBlur { 276 | vImageMatrixMultiply_ARGB8888(&effectOutBuffer, &effectInBuffer, saturationMatrix, Int32(divisor), nil, nil, vImage_Flags(kvImageNoFlags)) 277 | effectImageBuffersAreSwapped = true 278 | } else { 279 | vImageMatrixMultiply_ARGB8888(&effectInBuffer, &effectOutBuffer, saturationMatrix, Int32(divisor), nil, nil, vImage_Flags(kvImageNoFlags)) 280 | } 281 | } 282 | 283 | if !effectImageBuffersAreSwapped { 284 | effectImage = UIGraphicsGetImageFromCurrentImageContext() 285 | } 286 | 287 | UIGraphicsEndImageContext() 288 | 289 | if effectImageBuffersAreSwapped { 290 | effectImage = UIGraphicsGetImageFromCurrentImageContext() 291 | } 292 | 293 | UIGraphicsEndImageContext() 294 | } 295 | 296 | // Set up output context. 297 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 298 | let outputContext = UIGraphicsGetCurrentContext() 299 | outputContext?.scaleBy(x: 1, y: -1) 300 | outputContext?.translateBy(x: 0, y: -size.height) 301 | 302 | // Draw base image. 303 | outputContext?.draw(cgImage, in: imageRect) 304 | 305 | // Draw effect image. 306 | if let effectImage = effectImage?.cgImage, hasBlur { 307 | outputContext?.saveGState() 308 | if let image = maskImage?.cgImage { 309 | outputContext?.clip(to: imageRect, mask: image) 310 | } 311 | outputContext?.draw(effectImage, in: imageRect) 312 | outputContext?.restoreGState() 313 | } 314 | 315 | // Add in color tint. 316 | if let color = tintColor { 317 | outputContext?.saveGState() 318 | outputContext?.setFillColor(color.cgColor) 319 | outputContext?.fill(imageRect) 320 | outputContext?.restoreGState() 321 | } 322 | 323 | // Output image is ready. 324 | return UIGraphicsGetImageFromCurrentImageContext() 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /Sources/Int+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 11/19/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | extension Int { 29 | /// Determine if self is even (equivalent to `self % 2 == 0`) 30 | public var isEven: Bool { 31 | return (self % 2 == 0) 32 | } 33 | 34 | /// Determine if self is odd (equivalent to `self % 2 != 0`) 35 | public var isOdd: Bool { 36 | return (self % 2 != 0) 37 | } 38 | 39 | /// Determine if self is positive (equivalent to `self > 0`) 40 | public var isPositive: Bool { 41 | return (self > 0) 42 | } 43 | 44 | /// Determine if self is negative (equivalent to `self < 0`) 45 | public var isNegative: Bool { 46 | return (self < 0) 47 | } 48 | 49 | /** 50 | Convert self to a Double. 51 | 52 | Most useful when operating on Int optionals. 53 | 54 | For example: 55 | ``` 56 | let number: Int? = 5 57 | 58 | // instead of 59 | var double: Double? 60 | if let number = number { 61 | double = Double(number) 62 | } 63 | 64 | // use 65 | let double = number?.double 66 | ``` 67 | */ 68 | public var double: Double { 69 | return Double(self) 70 | } 71 | 72 | /** 73 | Convert self to a Float. 74 | 75 | Most useful when operating on Int optionals. 76 | 77 | For example: 78 | ``` 79 | let number: Int? = 5 80 | 81 | // instead of 82 | var float: Float? 83 | if let number = number { 84 | float = Float(number) 85 | } 86 | 87 | // use 88 | let float = number?.float 89 | ``` 90 | */ 91 | public var float: Float { 92 | return Float(self) 93 | } 94 | 95 | /** 96 | Convert self to a CGFloat. 97 | 98 | Most useful when operating on Int optionals. 99 | 100 | For example: 101 | ``` 102 | let number: Int? = 5 103 | 104 | // instead of 105 | var cgFloat: CGFloat? 106 | if let number = number { 107 | cgFloat = CGFloat(number) 108 | } 109 | 110 | // use 111 | let cgFloat = number?.cgFloat 112 | ``` 113 | */ 114 | public var cgFloat: CGFloat { 115 | return CGFloat(self) 116 | } 117 | 118 | /** 119 | Convert self to a String. 120 | 121 | Most useful when operating on Int optionals. 122 | 123 | For example: 124 | ``` 125 | let number: Int? = 5 126 | 127 | // instead of 128 | var string: String? 129 | if let number = number { 130 | string = String(number) 131 | } 132 | 133 | // use 134 | let string = number?.string 135 | ``` 136 | */ 137 | public var string: String { 138 | return String(self) 139 | } 140 | 141 | 142 | /** 143 | Convert self to an abbreviated String. 144 | 145 | Examples: 146 | ``` 147 | Value : 598 -> 598 148 | Value : -999 -> -999 149 | Value : 1000 -> 1K 150 | Value : -1284 -> -1.3K 151 | Value : 9940 -> 9.9K 152 | Value : 9980 -> 10K 153 | Value : 39900 -> 39.9K 154 | Value : 99880 -> 99.9K 155 | Value : 399880 -> 0.4M 156 | Value : 999898 -> 1M 157 | Value : 999999 -> 1M 158 | Value : 1456384 -> 1.5M 159 | Value : 12383474 -> 12.4M 160 | ``` 161 | 162 | - author: http://stackoverflow.com/a/35504720/1737738 163 | */ 164 | public var abbreviatedString: String { 165 | typealias Abbreviation = (threshold: Double, divisor: Double, suffix: String) 166 | let abbreviations: [Abbreviation] = [(0, 1, ""), 167 | (1000.0, 1000.0, "K"), 168 | (100_000.0, 1_000_000.0, "M"), 169 | (100_000_000.0, 1_000_000_000.0, "B")] 170 | // you can add more ! 171 | 172 | let startValue = Double(abs(self)) 173 | let abbreviation: Abbreviation = { 174 | var prevAbbreviation = abbreviations[0] 175 | for tmpAbbreviation in abbreviations where tmpAbbreviation.threshold <= startValue { 176 | prevAbbreviation = tmpAbbreviation 177 | } 178 | return prevAbbreviation 179 | }() 180 | 181 | let numFormatter = NumberFormatter() 182 | let value = Double(self) / abbreviation.divisor 183 | numFormatter.positiveSuffix = abbreviation.suffix 184 | numFormatter.negativeSuffix = abbreviation.suffix 185 | numFormatter.allowsFloats = true 186 | numFormatter.minimumIntegerDigits = 1 187 | numFormatter.minimumFractionDigits = 0 188 | numFormatter.maximumFractionDigits = 1 189 | 190 | return numFormatter.string(from: NSNumber(value: value)) ?? "\(self)" 191 | } 192 | 193 | 194 | /** 195 | Repeat a block `self` times. 196 | 197 | - parameter block: The block to execute (includes the current execution index) 198 | */ 199 | public func `repeat`(_ block: (Int) throws -> Void) rethrows { 200 | guard self > 0 else { return } 201 | try (1...self).forEach(block) 202 | } 203 | 204 | /// Generate a random Int bounded by a closed interval range. 205 | public static func random(_ range: ClosedRange) -> Int { 206 | return range.lowerBound + Int(arc4random_uniform(UInt32(range.upperBound - range.lowerBound + 1))) 207 | } 208 | 209 | /// Generate a random Int bounded by a range from min to max. 210 | public static func random(min: Int, max: Int) -> Int { 211 | return random(min...max) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Sources/NSObject+Customizable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Customizable.swift 3 | // 4 | // Created by Jonathan Landon on 1/19/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | /** 31 | Describes a protocol to enable objects to be customized, typically chained at initialization time. 32 | 33 | Inspired by: https://github.com/devxoul/Then 34 | */ 35 | public protocol Customizable {} 36 | 37 | extension Customizable { 38 | 39 | /** 40 | Set properties on `self` with a closure. 41 | 42 | Example: 43 | ``` 44 | let label = UILabel(frame: .zero).customize { 45 | $0.text = "Text goes here..." 46 | $0.textColor = .black 47 | $0.sizeToFit() 48 | $0.center = view.center 49 | $0.add(toSuperview: $0) 50 | } 51 | ``` 52 | 53 | - parameter customize: A closure, performing the customization. 54 | 55 | - returns: `self` 56 | */ 57 | @discardableResult 58 | public func customize(_ customize: (Self) -> Void) -> Self { 59 | customize(self) 60 | return self 61 | } 62 | } 63 | 64 | extension NSObject: Customizable {} 65 | -------------------------------------------------------------------------------- /Sources/NSObject+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 11/19/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | extension NSObject { 31 | /** 32 | The class name of self. 33 | 34 | Determines the class name of the object's dynamic type. 35 | */ 36 | public var className: String { 37 | return type(of: self).className 38 | } 39 | 40 | /** 41 | The class name of Self. 42 | 43 | Equivalent to: `NSStringFromClass(type).componentsSeparatedByString(".").last!` 44 | */ 45 | public static var className: String { 46 | return stringFromClass(self) 47 | } 48 | } 49 | 50 | private func stringFromClass(_ type: AnyClass) -> String { 51 | return NSStringFromClass(type).components(separatedBy: ".").last! 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Operators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Operators.swift 3 | // 4 | // Created by Jonathan Landon on 6/20/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | postfix operator % 29 | 30 | public postfix func %(value: CGFloat) -> CGFloat { 31 | return value / 100 32 | } 33 | 34 | public postfix func %(value: Float) -> Float { 35 | return value / 100 36 | } 37 | 38 | public postfix func %(value: Double) -> Double { 39 | return value / 100 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Optional+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Optional+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 11/19/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | extension Optional { 29 | /** 30 | Attempts to unwrap the optional, and executes the closure if a value exists 31 | 32 | - parameter block: The closure to execute if a value is found 33 | */ 34 | public func unwrap(_ block: (Wrapped) throws -> Void) rethrows { 35 | if let value = self { 36 | try block(value) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/StoreKit/SKProduct+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SKProduct+Extensions.swift 3 | // 4 | // Created by John C. "Hsoi" Daub (john.daub@ovenbits.com, hsoi@hsoienterprises.com) on 2015-03-05. 5 | // 6 | // Extensions to the SKProduct class. 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Copyright (c) 2014-2016 Oven Bits, LLC 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in all 20 | // copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | // SOFTWARE. 29 | 30 | 31 | import Foundation 32 | import StoreKit 33 | 34 | extension SKProduct { 35 | 36 | /** 37 | Returns the product's price, properly localized for display. 38 | 39 | - SeeAlso: Listing 2-3 @ https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/ShowUI.html#//apple_ref/doc/uid/TP40008267-CH3-SW10 40 | */ 41 | public var localizedPrice : String { 42 | let numberFormatter = NumberFormatter() 43 | numberFormatter.formatterBehavior = .behavior10_4 44 | numberFormatter.numberStyle = .currency 45 | numberFormatter.locale = priceLocale 46 | 47 | return numberFormatter.string(from: price) ?? "" 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /Sources/String+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 4/15/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | extension String { 31 | 32 | /// Converts self to an unsigned byte array. 33 | public var bytes: [UInt8] { 34 | return utf8.map { $0 } 35 | } 36 | 37 | /** 38 | Converts string to camel-case. 39 | 40 | Examples: 41 | 42 | ``` 43 | "os version".camelCasedString // "osVersion" 44 | "HelloWorld".camelCasedString // "helloWorld" 45 | "someword With Characters".camelCasedString // "somewordWithCharacters" 46 | ``` 47 | */ 48 | public var camelCased: String { 49 | guard !characters.isEmpty else { return self } 50 | 51 | if characters.contains(" ") { 52 | let first = self[0].lowercased() 53 | let cammel = capitalized.replacingOccurrences(of: " ", with: "") 54 | let rest = String(cammel.characters.dropFirst()) 55 | return first + rest 56 | } else { 57 | let first = self[0].lowercased() 58 | let rest = String(characters.dropFirst()) 59 | return first + rest 60 | } 61 | } 62 | 63 | /** 64 | The base64 encoded version of self. 65 | Credit: http://stackoverflow.com/a/29365954 66 | */ 67 | public var base64Encoded: String? { 68 | let utf8str = data(using: .utf8) 69 | return utf8str?.base64EncodedString() 70 | } 71 | 72 | /** 73 | The decoded value of a base64 encoded string 74 | Credit: http://stackoverflow.com/a/29365954 75 | */ 76 | public var base64Decoded: String? { 77 | guard let data = Data(base64Encoded: self, options: []) else { return nil } 78 | return String(data: data, encoding: .utf8) 79 | } 80 | 81 | /** 82 | Returns true if every character within the string is a numeric character. Empty strings are 83 | considered non-numeric. 84 | */ 85 | public var isNumeric: Bool { 86 | guard !isEmpty else { return false } 87 | return trimmingCharacters(in: .decimalDigits).isEmpty 88 | } 89 | 90 | /** 91 | Replaces all occurences of the pattern on self in-place. 92 | 93 | Examples: 94 | 95 | ``` 96 | "hello".regexInPlace("[aeiou]", "*") // "h*ll*" 97 | "hello".regexInPlace("([aeiou])", "<$1>") // "hll" 98 | ``` 99 | */ 100 | public mutating func formRegex(_ pattern: String, _ replacement: String) { 101 | do { 102 | let expression = try NSRegularExpression(pattern: pattern, options: []) 103 | let range = NSRange(location: 0, length: characters.count) 104 | self = expression.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replacement) 105 | } 106 | catch { return } 107 | } 108 | 109 | /** 110 | Returns a string containing replacements for all pattern matches. 111 | 112 | Examples: 113 | 114 | ``` 115 | "hello".regex("[aeiou]", "*") // "h*ll*" 116 | "hello".regex("([aeiou])", "<$1>") // "hll" 117 | ``` 118 | */ 119 | public func regex(_ pattern: String, _ replacement: String) -> String { 120 | var replacementString = self 121 | replacementString.formRegex(pattern, replacement) 122 | return replacementString 123 | } 124 | 125 | /** 126 | Replaces pattern-matched strings, operated upon by a closure, on self in-place. 127 | 128 | - parameter pattern: The pattern to match against. 129 | - parameter matches: The closure in which to handle matched strings. 130 | 131 | Example: 132 | 133 | ``` 134 | "hello".regexInPlace(".") { 135 | let s = $0.unicodeScalars 136 | let v = s[s.startIndex].value 137 | return "\(v) " 138 | } // "104 101 108 108 111 " 139 | */ 140 | public mutating func formRegex(_ pattern: String, _ matches: (String) -> String) { 141 | 142 | let expression: NSRegularExpression 143 | do { 144 | expression = try NSRegularExpression(pattern: "(\(pattern))", options: []) 145 | } 146 | catch { 147 | print("regex error: \(error)") 148 | return 149 | } 150 | 151 | let range = NSMakeRange(0, self.characters.count) 152 | 153 | var startOffset = 0 154 | 155 | let results = expression.matches(in: self, options: [], range: range) 156 | 157 | for result in results { 158 | 159 | var endOffset = startOffset 160 | 161 | for i in 1.. String) -> String { 199 | var replacementString = self 200 | replacementString.formRegex(pattern, matches) 201 | return replacementString 202 | } 203 | 204 | /// Substring at index 205 | public subscript(i: Int) -> String { 206 | return String(self[index(startIndex, offsetBy: i)]) 207 | } 208 | 209 | /// Substring for range 210 | public subscript(r: Range) -> String { 211 | return substring(with: index(startIndex, offsetBy: r.lowerBound) ..< index(startIndex, offsetBy: r.upperBound)) 212 | } 213 | 214 | /// Substring for closed range 215 | public subscript(r: ClosedRange) -> String { 216 | return substring(with: index(startIndex, offsetBy: r.lowerBound) ..< index(startIndex, offsetBy: r.upperBound + 1)) 217 | } 218 | 219 | /** 220 | Truncates the string to length characters, optionally appending a trailing string. If the string is shorter 221 | than the required length, then this function is a non-op. 222 | 223 | - parameter length: The length of string required. 224 | - parameter trailing: An optional addition to the end of the string (increasing "length"), such as ellipsis. 225 | 226 | - returns: The truncated string. 227 | 228 | Examples: 229 | 230 | ``` 231 | "hello there".truncated(to: 5) // "hello" 232 | "hello there".truncated(to: 5, trailing: "...") // "hello..." 233 | ``` 234 | 235 | */ 236 | public func truncated(to length: Int, trailing: String = "") -> String { 237 | guard !characters.isEmpty && characters.count > length else { return self } 238 | return self.substring(to: index(startIndex, offsetBy: length)) + trailing 239 | } 240 | 241 | public mutating func truncate(to length: Int, trailing: String = "") { 242 | self = truncated(to: length, trailing: trailing) 243 | } 244 | 245 | /** 246 | A bridge for invoking `String.localizedStandardContainsString()`, which is available in iOS 9 and later. If you need to 247 | support iOS versions prior to iOS 9, use `compatibleStandardContainsString()` as a means to bridge functionality. 248 | If you can support iOS 9 or greater only, use `localizedStandardContainsString()` directly. 249 | 250 | From Apple's Swift 2.1 documentation: 251 | 252 | `localizedStandardContainsString()` is the most appropriate method for doing user-level string searches, similar to how searches are done generally in the system. The search is locale-aware, case and diacritic insensitive. The exact list of search options applied may change over time. 253 | 254 | - parameter string: The string to determine if is contained by self. 255 | 256 | - returns: Returns true if self contains string, taking the current locale into account. 257 | */ 258 | public func compatibleStandardContains(_ string: String) -> Bool { 259 | if #available(iOS 9.0, *) { 260 | return localizedStandardContains(string) 261 | } 262 | return range(of: string, options: [.caseInsensitive, .diacriticInsensitive], locale: .current) != nil 263 | } 264 | 265 | /** 266 | Convert an NSRange to a Range. There is still a mismatch between the regular expression libraries 267 | and NSString/String. This makes it easier to convert between the two. Using this allows complex 268 | strings (including emoji, regonial indicattors, etc.) to be manipulated without having to resort 269 | to NSString instances. 270 | 271 | Note that it may not always be possible to convert from an NSRange as they are not exactly the same. 272 | 273 | Taken from: 274 | http://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index 275 | 276 | - parameter nsRange: The NSRange instance to covert to a Range. 277 | 278 | - returns: The Range, if it was possible to convert. Otherwise nil. 279 | */ 280 | public func range(from nsRange: NSRange) -> Range? { 281 | guard 282 | let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex), 283 | let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex), 284 | let from = String.Index(from16, within: self), 285 | let to = String.Index(to16, within: self) 286 | else { return nil } 287 | 288 | return from ..< to 289 | } 290 | 291 | /** 292 | Convert a Range to an NSRange. There is still a mismatch between the regular expression libraries 293 | and NSString/String. This makes it easier to convert between the two. Using this allows complex 294 | strings (including emoji, regonial indicattors, etc.) to be manipulated without having to resort 295 | to NSString instances. 296 | 297 | Taken from: 298 | http://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index 299 | 300 | - parameter range: The Range instance to conver to an NSRange. 301 | 302 | - returns: The NSRange converted from the input. This will always succeed. 303 | */ 304 | public func nsRange(from range: Range) -> NSRange { 305 | let from = String.UTF16View.Index(range.lowerBound, within: utf16) 306 | let to = String.UTF16View.Index(range.upperBound, within: utf16) 307 | return NSRange(location: utf16.distance(from: utf16.startIndex, to: from), length: utf16.distance(from: from, to: to)) 308 | } 309 | 310 | } 311 | -------------------------------------------------------------------------------- /Sources/UIAlertController+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertController+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 11/1/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | import UIKit 30 | 31 | extension UIAlertController { 32 | 33 | /** 34 | Adds a UIAlertAction, initialized with a title, style, and completion handler. 35 | 36 | - parameter title: The text to use for the action title. 37 | - parameter style: The style to use for the action (optional, defaults to `.default`). 38 | - parameter handler: A closure to execute when the user selects the action (optional, defaults to `nil`). 39 | 40 | - returns: self 41 | */ 42 | @discardableResult 43 | public func addAction(title: String?, style: UIAlertActionStyle = .default, handler: ((UIAlertAction) -> Void)? = nil) -> Self { 44 | addAction(UIAlertAction(title: title, style: style, handler: handler)) 45 | return self 46 | } 47 | 48 | /** 49 | Creates a UIAlertController instance, styled as an action sheet. 50 | 51 | - parameter title: The title for the controller (optional, defaults to `nil`). 52 | - parameter message: The message for the controller (optional, defaults to `nil`). 53 | 54 | - returns: A UIAlertController instance, styled as an action sheet. 55 | */ 56 | public static func actionSheet(title: String? = nil, message: String? = nil) -> UIAlertController { 57 | return UIAlertController(title: title, message: message, preferredStyle: .actionSheet) 58 | } 59 | 60 | /** 61 | Creates a UIAlertController instance, styled as an alert. 62 | 63 | - parameter title: The title for the controller (optional, defaults to `nil`). 64 | - parameter message: The message for the controller (optional, defaults to `nil`). 65 | 66 | - returns: A UIAlertController instance, styled as an alert. 67 | */ 68 | public static func alert(title: String? = nil, message: String? = nil) -> UIAlertController { 69 | return UIAlertController(title: title, message: message, preferredStyle: .alert) 70 | } 71 | 72 | /** 73 | Presents the UIAlertController inside the specified view controller. 74 | 75 | - parameter controller: The controller in which to present the alert controller (optional, defaults to `.current`). 76 | - parameter animated: Pass `true` to animate the presentation; otherwise, pass `false` (optional, defaults to `true`). 77 | - parameter completion: The closure to execute after the presentation finishes (optional, defaults to `nil`). 78 | */ 79 | public func present(in controller: UIViewController? = .current, animated: Bool = true, completion: (() -> Void)? = nil) { 80 | controller?.present(self, animated: animated, completion: completion) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/UIButton+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 9/22/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | extension UIButton { 31 | /** 32 | Sets the background color to use for the specified button state. 33 | 34 | - parameter color: The background color to use for the specified state. 35 | - parameter state: The state that uses the specified image. 36 | */ 37 | public func setBackgroundColor(_ color: UIColor, for state: UIControlState) { 38 | setBackgroundImage(UIImage(color: color), for: state) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/UICollectionView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 11/19/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | extension UICollectionView { 31 | 32 | // MARK: Cells 33 | 34 | /** 35 | Registers a UICollectionViewCell for use in a UICollectionView. 36 | 37 | - parameter type: The type of cell to register. 38 | - parameter reuseIdentifier: The reuse identifier for the cell (optional). 39 | 40 | By default, the class name of the cell is used as the reuse identifier. 41 | 42 | Example: 43 | ``` 44 | class CustomCell: UICollectionViewCell {} 45 | 46 | let collectionView = UICollectionView() 47 | 48 | // registers the CustomCell class with a reuse identifier of "CustomCell" 49 | collectionView.registerCell(CustomCell) 50 | ``` 51 | */ 52 | public func registerCell(_ type: T.Type, withIdentifier reuseIdentifier: String = String(describing: T.self)) { 53 | register(T.self, forCellWithReuseIdentifier: reuseIdentifier) 54 | } 55 | 56 | /** 57 | Dequeues a UICollectionViewCell for use in a UICollectionView. 58 | 59 | - parameter type: The type of the cell. 60 | - parameter indexPath: The index path at which to dequeue a new cell. 61 | - parameter reuseIdentifier: The reuse identifier for the cell (optional). 62 | 63 | - returns: A force-casted UICollectionViewCell of the specified type. 64 | 65 | By default, the class name of the cell is used as the reuse identifier. 66 | 67 | Example: 68 | ``` 69 | class CustomCell: UICollectionViewCell {} 70 | 71 | let collectionView = UICollectionView() 72 | 73 | // dequeues a CustomCell class 74 | let cell = collectionView.dequeueReusableCell(CustomCell.self, forIndexPath: indexPath) 75 | ``` 76 | */ 77 | public func dequeueCell(_ type: T.Type = T.self, 78 | withIdentifier reuseIdentifier: String = String(describing: T.self), 79 | for indexPath: IndexPath) -> T 80 | { 81 | guard let cell = dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? T else { 82 | fatalError("Unknown cell type (\(T.self)) for reuse identifier: \(reuseIdentifier)") 83 | } 84 | return cell 85 | } 86 | 87 | // MARK: Headers 88 | 89 | /** 90 | Registers a UICollectionReusableView for use in a UICollectionView section header. 91 | 92 | - parameter type: The type of header view to register. 93 | - parameter reuseIdentifier: The reuse identifier for the header view (optional). 94 | 95 | By default, the class name of the header view is used as the reuse identifier. 96 | 97 | Example: 98 | ``` 99 | class CustomHeader: UICollectionReusableView {} 100 | 101 | let collectionView = UICollectionView() 102 | 103 | // registers the CustomCell class with a reuse identifier of "CustomHeader" 104 | collectionView.registerHeader(CustomHeader) 105 | ``` 106 | */ 107 | public func registerHeader(_ type: T.Type, withIdentifier reuseIdentifier: String = String(describing: T.self)) { 108 | register(T.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: reuseIdentifier) 109 | } 110 | 111 | /** 112 | Dequeues a UICollectionReusableView for use in a UICollectionView section header. 113 | 114 | - parameter type: The type of the header view. 115 | - parameter indexPath: The index path at which to dequeue a new header view. 116 | - parameter reuseIdentifier: The reuse identifier for the header view (optional). 117 | 118 | - returns: A force-casted UICollectionReusableView of the specified type. 119 | 120 | By default, the class name of the header view is used as the reuse identifier. 121 | 122 | Example: 123 | ``` 124 | class CustomHeader: UICollectionReusableView {} 125 | 126 | let collectionView = UICollectionView() 127 | 128 | // dequeues a CustomHeader class 129 | let footerView = collectionView.dequeueReusableHeader(CustomHeader.self, forIndexPath: indexPath) 130 | ``` 131 | */ 132 | public func dequeueHeader(_ type: T.Type = T.self, 133 | withIdentifier reuseIdentifier: String = String(describing: T.self), 134 | for indexPath: IndexPath) -> T 135 | { 136 | guard let header = dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: reuseIdentifier, for: indexPath) as? T else { 137 | fatalError("Unknown header type (\(T.self)) for reuse identifier: \(reuseIdentifier)") 138 | } 139 | return header 140 | } 141 | 142 | // MARK: Footers 143 | 144 | /** 145 | Registers a UICollectionReusableView for use in a UICollectionView section footer. 146 | 147 | - parameter type: The type of footer view to register. 148 | - parameter reuseIdentifier: The reuse identifier for the footer view (optional). 149 | 150 | By default, the class name of the footer view is used as the reuse identifier. 151 | 152 | Example: 153 | ``` 154 | class CustomFooter: UICollectionReusableView {} 155 | 156 | let collectionView = UICollectionView() 157 | 158 | // registers the CustomFooter class with a reuse identifier of "CustomFooter" 159 | collectionView.registerFooter(CustomFooter) 160 | ``` 161 | */ 162 | public func registerFooter(_ type: T.Type, withIdentifier reuseIdentifier: String = String(describing: T.self)) { 163 | register(T.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: reuseIdentifier) 164 | } 165 | 166 | /** 167 | Dequeues a UICollectionReusableView for use in a UICollectionView section footer. 168 | 169 | - parameter type: The type of the footer view. 170 | - parameter indexPath: The index path at which to dequeue a new footer view. 171 | - parameter reuseIdentifier: The reuse identifier for the footer view (optional). 172 | 173 | - returns: A force-casted UICollectionReusableView of the specified type. 174 | 175 | By default, the class name of the footer view is used as the reuse identifier. 176 | 177 | Example: 178 | ``` 179 | class CustomFooter: UICollectionReusableView {} 180 | 181 | let collectionView = UICollectionView() 182 | 183 | // dequeues a CustomFooter class 184 | let footerView = collectionView.dequeueReusableFooter(CustomFooter.self, forIndexPath: indexPath) 185 | ``` 186 | */ 187 | public func dequeueFooter(_ type: T.Type = T.self, 188 | withIdentifier reuseIdentifier: String = String(describing: T.self), 189 | for indexPath: IndexPath) -> T 190 | { 191 | guard let footer = dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionFooter, withReuseIdentifier: reuseIdentifier, for: indexPath) as? T else { 192 | fatalError("Unknown footer type (\(T.self)) for reuse identifier: \(reuseIdentifier)") 193 | } 194 | return footer 195 | } 196 | 197 | /** 198 | Inserts rows into self. 199 | 200 | - parameter indices: The rows indices to insert into self. 201 | - parameter section: The section in which to insert the rows (optional, defaults to 0). 202 | - parameter completion: The completion handler, called after the rows have been inserted (optional). 203 | */ 204 | public func insert(_ indices: [Int], section: Int = 0, completion: @escaping (Bool) -> Void = { _ in }) { 205 | guard !indices.isEmpty else { return } 206 | 207 | let indexPaths = indices.map { IndexPath(row: $0, section: section) } 208 | performBatchUpdates({ self.insertItems(at: indexPaths) }, completion: completion) 209 | } 210 | 211 | /** 212 | Deletes rows from self. 213 | 214 | - parameter indices: The rows indices to delete from self. 215 | - parameter section: The section in which to delete the rows (optional, defaults to 0). 216 | - parameter completion: The completion handler, called after the rows have been deleted (optional). 217 | */ 218 | public func delete(_ indices: [Int], section: Int = 0, completion: @escaping (Bool) -> Void = { _ in }) { 219 | guard !indices.isEmpty else { return } 220 | 221 | let indexPaths = indices.map { IndexPath(row: $0, section: section) } 222 | performBatchUpdates({ self.deleteItems(at: indexPaths) }, completion: completion) 223 | } 224 | 225 | /** 226 | Reloads rows in self. 227 | 228 | - parameter indices: The rows indices to reload in self. 229 | - parameter section: The section in which to reload the rows (optional, defaults to 0). 230 | - parameter completion: The completion handler, called after the rows have been reloaded (optional). 231 | */ 232 | public func reload(_ indices: [Int], section: Int = 0, completion: @escaping (Bool) -> Void = { _ in }) { 233 | guard !indices.isEmpty else { return } 234 | 235 | let indexPaths = indices.map { IndexPath(row: $0, section: section) } 236 | performBatchUpdates({ self.reloadItems(at: indexPaths) }, completion: completion) 237 | } 238 | } 239 | 240 | 241 | // MARK: - IndexPathTraversing 242 | 243 | extension UICollectionView { 244 | 245 | /// The minimum ("starting") `IndexPath` for traversing a `UICollectionView` "sequentially". 246 | public var minimumIndexPath: IndexPath { 247 | return IndexPath(item: 0, section: 0) 248 | } 249 | 250 | /// The maximum ("ending") `IndexPath` for traversing a `UICollectionView` "sequentially". 251 | public var maximumIndexPath: IndexPath { 252 | let lastSection = max(0, numberOfSections - 1) 253 | let lastItem = max(0, numberOfItems(inSection: lastSection) - 1) 254 | return IndexPath(item: lastItem, section: lastSection) 255 | } 256 | 257 | 258 | /** 259 | When "sequentially" traversing a `UICollectionView`, what's the next `IndexPath` after the given `IndexPath`. 260 | 261 | - parameter indexPath: The current indexPath; the path we want to find what comes after. 262 | 263 | - returns: The next indexPath, or nil if we're at the maximumIndexPath 264 | - SeeAlso: `var maximumIndexpath` 265 | */ 266 | public func indexPath(after indexPath: IndexPath) -> IndexPath? { 267 | if indexPath == maximumIndexPath { 268 | return nil 269 | } 270 | 271 | assertIsValid(indexPath: indexPath) 272 | 273 | let lastItem = numberOfItems(inSection: indexPath.section) 274 | if indexPath.item == lastItem { 275 | return IndexPath(item: 0, section: indexPath.section + 1) 276 | } else { 277 | return IndexPath(item: indexPath.item + 1, section: indexPath.section) 278 | } 279 | } 280 | 281 | /** 282 | When "sequentially" traversing a `UICollectionView`, what's the previous `IndexPath` before the given `IndexPath`. 283 | 284 | - parameter indexPath: The current indexPath; the path we want to find what comes before. 285 | 286 | - returns: The prior indexPath, or nil if we're at the minimumIndexPath 287 | - SeeAlso: `var minimumIndexPath` 288 | */ 289 | public func indexPath(before indexPath: IndexPath) -> IndexPath? { 290 | if indexPath == minimumIndexPath { 291 | return nil 292 | } 293 | 294 | assertIsValid(indexPath: indexPath) 295 | 296 | if indexPath.item == 0 { 297 | let lastItem = numberOfItems(inSection: indexPath.section - 1) 298 | return IndexPath(item: lastItem, section: indexPath.section - 1) 299 | } else { 300 | return IndexPath(item: indexPath.item - 1, section: indexPath.section) 301 | } 302 | } 303 | 304 | private func assertIsValid(indexPath: IndexPath, file: StaticString = #file, line: UInt = #line) { 305 | let maxPath = maximumIndexPath 306 | assert( 307 | indexPath.section <= maxPath.section && indexPath.section >= 0, 308 | "Index path \(indexPath) is outside the bounds set by the minimum (\(minimumIndexPath)) and maximum (\(maxPath)) index path", 309 | file: file, 310 | line: line 311 | ) 312 | let itemCount = numberOfItems(inSection: indexPath.section) 313 | assert( 314 | indexPath.item < itemCount && indexPath.item >= 0, 315 | "Index path \(indexPath) item index is outside the bounds of the items (\(itemCount)) in the indexPath's section", 316 | file: file, 317 | line: line 318 | ) 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /Sources/UIControl+Actionable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIControl+Actionable.swift 3 | // 4 | // Created by Alex Corcoran on 6/22/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | extension UIControl { 29 | 30 | private final class Action { 31 | var action: () -> Void 32 | 33 | init(action: @escaping () -> Void) { 34 | self.action = action 35 | } 36 | } 37 | 38 | private struct AssociatedKeys { 39 | static var ActionName = "action" 40 | } 41 | 42 | private var action: Action? { 43 | set { objc_setAssociatedObject(self, &AssociatedKeys.ActionName, newValue, .OBJC_ASSOCIATION_RETAIN) } 44 | get { return objc_getAssociatedObject(self, &AssociatedKeys.ActionName) as? Action } 45 | } 46 | 47 | /** 48 | Initialize a UIControl, using the given closure as the .TouchUpInside target/action event. 49 | 50 | - parameter action: The closure to execute upon control press. 51 | 52 | - returns: An initialized UIControl. 53 | */ 54 | public convenience init(actionClosure: @escaping () -> Void) { 55 | self.init() 56 | action = Action(action: actionClosure) 57 | addTarget(self, action: #selector(handleAction), for: .touchUpInside) 58 | } 59 | 60 | 61 | /** 62 | Initialize a UIControl with the given frame, using the given closure as the .TouchUpInside target/action event. 63 | 64 | - parameter frame: The frame of the control. 65 | - parameter action: the closure to execute upon control press. 66 | 67 | - returns: An initialized UIControl. 68 | */ 69 | public convenience init(frame: CGRect, actionClosure: @escaping () -> Void) { 70 | self.init(frame: frame) 71 | action = Action(action: actionClosure) 72 | addTarget(self, action: #selector(handleAction), for: .touchUpInside) 73 | } 74 | 75 | 76 | /** 77 | Adds the given closure as the control's target action 78 | 79 | - parameter controlEvents: The UIControlEvents upon which to execute this action. 80 | - parameter action: The action closure to execute. 81 | */ 82 | public func addTarget(for controlEvents: UIControlEvents, actionClosure: @escaping () -> Void) { 83 | action = Action(action: actionClosure) 84 | addTarget(self, action: #selector(handleAction), for: controlEvents) 85 | } 86 | 87 | public func handleAction() { 88 | action?.action() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/UIFont+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 4/15/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import UIKit 29 | 30 | extension UIFont { 31 | 32 | public enum Extension { 33 | case otf 34 | case ttf 35 | case woff 36 | case custom(String) 37 | 38 | fileprivate var type: String { 39 | switch self { 40 | case .otf: return "otf" 41 | case .ttf: return "ttf" 42 | case .woff: return "woff" 43 | case .custom(let value): return value 44 | } 45 | } 46 | } 47 | 48 | /** 49 | Registers a font for use in the app 50 | 51 | - parameter name: The name of the font family. 52 | - parameter fileExtension: The extension of the font file. 53 | - parameter bundle: The bundle in which the font file is located. 54 | */ 55 | public static func register(name: String, fileExtension: Extension, in bundle: Bundle) { 56 | guard 57 | let path = bundle.path(forResource: name, ofType: fileExtension.type), 58 | let fontData = try? Data(contentsOf: URL(fileURLWithPath: path)), 59 | let provider = CGDataProvider(data: fontData as CFData) 60 | else { 61 | print("Error registering font: \(name).\(fileExtension.type)") 62 | return 63 | } 64 | 65 | let font = CGFont(provider) 66 | 67 | var error: Unmanaged? 68 | guard !CTFontManagerRegisterGraphicsFont(font, &error) else { 69 | error?.release() 70 | return 71 | } 72 | 73 | if let errorRef = error?.takeRetainedValue() { 74 | let errorDescription = CFErrorCopyDescription(errorRef) 75 | print("Failed to load font: \(errorDescription) (\(name).\(fileExtension.type))") 76 | } 77 | 78 | error?.release() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/UIGestureRecognizer+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIGestureRecognizer+Extensions.swift 3 | // 4 | // Created by hsoi on 4/13/15. 5 | // 6 | // Extending UIGestureRecognizer -- at first, to add support for a block-based action instead of traditional target/action. 7 | // 8 | // Created by Hsoi, Swift-ized by JLandon. 9 | // 10 | // The MIT License (MIT) 11 | // 12 | // Copyright (c) 2014-2016 Oven Bits, LLC 13 | // 14 | // Permission is hereby granted, free of charge, to any person obtaining a copy 15 | // of this software and associated documentation files (the "Software"), to deal 16 | // in the Software without restriction, including without limitation the rights 17 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | // copies of the Software, and to permit persons to whom the Software is 19 | // furnished to do so, subject to the following conditions: 20 | // 21 | // The above copyright notice and this permission notice shall be included in all 22 | // copies or substantial portions of the Software. 23 | // 24 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | // SOFTWARE. 31 | 32 | 33 | import Foundation 34 | 35 | extension UIGestureRecognizer { 36 | private class GestureAction { 37 | var action: (UIGestureRecognizer) -> Void 38 | 39 | init(action: @escaping (UIGestureRecognizer) -> Void) { 40 | self.action = action 41 | } 42 | } 43 | 44 | private struct AssociatedKeys { 45 | static var ActionName = "action" 46 | } 47 | 48 | private var gestureAction: GestureAction? { 49 | set { objc_setAssociatedObject(self, &AssociatedKeys.ActionName, newValue, .OBJC_ASSOCIATION_RETAIN) } 50 | get { return objc_getAssociatedObject(self, &AssociatedKeys.ActionName) as? GestureAction } 51 | } 52 | 53 | /** 54 | Convenience initializer, associating an action closure with the gesture recognizer (instead of the more traditional target/action). 55 | 56 | - parameter action: The closure for the recognizer to execute. There is no pre-logic to conditionally invoke the closure or not (e.g. only invoke the closure if the gesture recognizer is in a particular state). The closure is merely invoked directly; all handler logic is up to the closure. 57 | 58 | - returns: The UIGestureRecognizer. 59 | */ 60 | public convenience init(action: @escaping (UIGestureRecognizer) -> Void) { 61 | self.init() 62 | gestureAction = GestureAction(action: action) 63 | addTarget(self, action: #selector(handleAction(_:))) 64 | } 65 | 66 | dynamic private func handleAction(_ recognizer: UIGestureRecognizer) { 67 | gestureAction?.action(recognizer) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/UIImage+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Extensions.swift 3 | // 4 | // Created by John C. "Hsoi" Daub (john.daub@ovenbits.com, hsoi@hsoienterprises.com) on 2014-11-04. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import UIKit 29 | 30 | extension UIImage { 31 | 32 | /** 33 | Returns a copy of self, tinted by the given color. 34 | 35 | - parameter tintColor: The UIColor to tint by. 36 | - returns: A copy of self, tinted by the tintColor. 37 | */ 38 | public func tinted(_ tintColor: UIColor) -> UIImage { 39 | guard let cgImage = cgImage else { return self } 40 | 41 | UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) 42 | 43 | defer { UIGraphicsEndImageContext() } 44 | 45 | let context = UIGraphicsGetCurrentContext() 46 | context?.saveGState() 47 | 48 | tintColor.setFill() 49 | 50 | context?.translateBy(x: 0, y: size.height) 51 | context?.scaleBy(x: 1, y: -1) 52 | context?.setBlendMode(.normal) 53 | 54 | let rect = CGRect(origin: .zero, size: size) 55 | context?.draw(cgImage, in: rect) 56 | 57 | context?.clip(to: rect, mask: cgImage) 58 | context?.addRect(rect) 59 | context?.drawPath(using: .fill) 60 | context?.restoreGState() 61 | 62 | return UIGraphicsGetImageFromCurrentImageContext() ?? self 63 | } 64 | 65 | /** 66 | Returns a copy of self, scaled by a specified scale factor (with an optional image orientation). 67 | 68 | Example: 69 | ``` 70 | let image = UIImage(named: ) // image size = (width: 200, height: 100) 71 | image?.resized(width: 50, height: 50) // image size = (width: 50, height: 50) 72 | image?.resized(width: 50, height: 50, maintainAspectRatio: true) // image size = (width: 50, height: 25) 73 | ``` 74 | 75 | - parameter width: The width to which to resize the image. 76 | - parameter height: The height to which to resize the image. 77 | - parameter maintainAspectRatio: A Boolean flag indicating whether or not to maintain the image's aspect ratio when resizing (optional, defaults to `false`). 78 | - returns: A copy of self, resized to a specified width and heigh (with an option to maintain the original aspect ratio). 79 | */ 80 | public func resized(width: CGFloat, height: CGFloat, maintainAspectRatio: Bool = false) -> UIImage? { 81 | 82 | let inputSize = CGSize(width: width, height: height) 83 | let newSize: CGSize 84 | 85 | if maintainAspectRatio { 86 | let ratio = min(inputSize.width / size.width, inputSize.height / size.height) 87 | newSize = CGSize(width: size.width * ratio, height: size.height * ratio) 88 | } 89 | else { 90 | newSize = inputSize 91 | } 92 | 93 | UIGraphicsBeginImageContextWithOptions(newSize, false, UIScreen.main.scale) 94 | 95 | defer { UIGraphicsEndImageContext() } 96 | 97 | draw(in: CGRect(origin: .zero, size: newSize)) 98 | 99 | return UIGraphicsGetImageFromCurrentImageContext() 100 | } 101 | 102 | /** 103 | Returns a copy of self, scaled by a specified scale factor (with an optional image orientation). 104 | 105 | Example: 106 | ``` 107 | let image = UIImage(named: ) // image size = (width: 200, height: 100) 108 | image?.scaled(by: 0.25) // image size = (width: 50, height: 25) 109 | image?.scaled(by: 2) // image size = (width: 400, height: 200) 110 | ``` 111 | 112 | - parameter scaleFactor: The factor at which to scale this image. 113 | - parameter orientiation: The orientation to use for the scaled image (optional, defaults to the image's `imageOrientation` property). 114 | - returns: A copy of self, scaled by the scaleFactor (with an optional image orientation). 115 | */ 116 | public func scaled(by scaleFactor: CGFloat, withOrientation orientation: UIImageOrientation? = nil) -> UIImage? { 117 | guard let coreImage = cgImage else { return nil } 118 | 119 | return UIImage(cgImage: coreImage, scale: 1/scaleFactor, orientation: orientation ?? imageOrientation) 120 | } 121 | 122 | /** 123 | Returns an optional image using the `drawingCommands` 124 | 125 | Example: 126 | 127 | let image = UIImage(size: CGSize(width: 12, height: 24)) { 128 | let path = UIBezierPath() 129 | path.move(to: ...) 130 | path.addLine(to: ...) 131 | path.addLine(to: ...) 132 | path.lineWidth = 2 133 | UIColor.white.setStroke() 134 | path.stroke() 135 | } 136 | 137 | - parameter size: The image size. 138 | - parameter drawingCommands: A closure describing the path, fill/stroke color, etc. 139 | - returns: An optional image based on `drawingCommands`. 140 | */ 141 | public convenience init?(size: CGSize, drawingCommands commands: () -> Void) { 142 | UIGraphicsBeginImageContextWithOptions(size, false, 0) 143 | commands() 144 | 145 | defer { UIGraphicsEndImageContext() } 146 | 147 | guard let image = UIGraphicsGetImageFromCurrentImageContext()?.cgImage else { return nil } 148 | 149 | self.init(cgImage: image) 150 | } 151 | 152 | /** 153 | Returns an optional image using the specified color 154 | 155 | - parameter color: The color of the image. 156 | - returns: An optional image based on `color`. 157 | */ 158 | public convenience init?(color: UIColor) { 159 | let size = CGSize(width: 1, height: 1) 160 | let rect = CGRect(origin: .zero, size: size) 161 | UIGraphicsBeginImageContextWithOptions(size, false, 0) 162 | 163 | color.setFill() 164 | UIRectFill(rect) 165 | 166 | defer { UIGraphicsEndImageContext() } 167 | 168 | guard let image = UIGraphicsGetImageFromCurrentImageContext()?.cgImage else { return nil } 169 | 170 | self.init(cgImage: image) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Sources/UIScreen+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScreen+Extensions.swift 3 | // 4 | // Created by John C. "Hsoi" Daub (john.daub@ovenbits.com, hsoi@hsoienterprises.com) on 2014-10-09. 5 | // 6 | // Extensions to the UIScreen class. 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Copyright (c) 2014-2016 Oven Bits, LLC 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in all 20 | // copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | // SOFTWARE. 29 | 30 | import UIKit 31 | 32 | extension UIScreen { 33 | 34 | /// The center of the screen 35 | public var center: CGPoint { 36 | return CGPoint(x: width/2, y: height/2) 37 | } 38 | 39 | /// The width of the screen 40 | public var width: CGFloat { 41 | return bounds.width 42 | } 43 | 44 | /// The height of the screen 45 | public var height: CGFloat { 46 | return bounds.height 47 | } 48 | 49 | /// The center of the main screen 50 | @available(*, deprecated: 2.0, message: "use main.center") 51 | public static var mainCenter: CGPoint { 52 | return UIScreen.main.center 53 | } 54 | 55 | /// The width of the main screen 56 | @available(*, deprecated: 2.0, message: "use main.width") 57 | public static var mainWidth: CGFloat { 58 | return UIScreen.main.width 59 | } 60 | 61 | /// The height of the main screen 62 | @available(*, deprecated: 2.0, message: "use main.height") 63 | public static var mainHeight: CGFloat { 64 | return UIScreen.main.height 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/UIScrollView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+Extensions.swift 3 | // 4 | // Created by hsoi on 5/28/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | /** OBExtensions Extends UIScrollView 31 | 32 | */ 33 | extension UIScrollView { 34 | 35 | /// Immediately stops the scrollview scrolling. 36 | public func stopScrolling() { 37 | // http://stackoverflow.com/questions/3410777/how-can-i-programmatically-force-stop-scrolling-in-a-uiscrollview 38 | let offset = contentOffset 39 | setContentOffset(offset, animated: false) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Sources/UISplitViewController+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UISplitViewController+Extensions.swift 3 | // 4 | // Created by hsoi on 9/22/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | /** 31 | Oven Bits UISplitViewController extensions 32 | 33 | */ 34 | extension UISplitViewController { 35 | 36 | /// Obtain the master/primary UIViewController. 37 | public var masterViewController: UIViewController { 38 | return viewControllers[0] // Hsoi 2015-09-22 - AFAIK, there should ALWAYS be this one ViewController. 39 | } 40 | 41 | 42 | /// Obtain the detail/secondary UIViewController, if present. 43 | public var detailViewController: UIViewController? { 44 | guard viewControllers.count > 1 else { return nil } 45 | return viewControllers[1] 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Sources/UITableView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 11/19/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | extension UITableView { 31 | /** 32 | Registers a UITableViewCell for use in a UITableView. 33 | 34 | - parameter type: The type of cell to register. 35 | - parameter reuseIdentifier: The reuse identifier for the cell (optional). 36 | 37 | By default, the class name of the cell is used as the reuse identifier. 38 | 39 | Example: 40 | ``` 41 | class CustomCell: UITableViewCell {} 42 | 43 | let tableView = UITableView() 44 | 45 | // registers the CustomCell class with a reuse identifier of "CustomCell" 46 | tableView.registerCell(CustomCell) 47 | ``` 48 | */ 49 | public func registerCell(_ type: T.Type, withIdentifier reuseIdentifier: String = String(describing: T.self)) { 50 | register(T.self, forCellReuseIdentifier: reuseIdentifier) 51 | } 52 | 53 | /** 54 | Dequeues a UITableViewCell for use in a UITableView. 55 | 56 | - parameter type: The type of the cell. 57 | - parameter reuseIdentifier: The reuse identifier for the cell (optional). 58 | 59 | - returns: A force-casted UITableViewCell of the specified type. 60 | 61 | By default, the class name of the cell is used as the reuse identifier. 62 | 63 | Example: 64 | ``` 65 | class CustomCell: UITableViewCell {} 66 | 67 | let tableView = UITableView() 68 | 69 | // dequeues a CustomCell class 70 | let cell = tableView.dequeueReusableCell(CustomCell) 71 | ``` 72 | */ 73 | public func dequeueCell(_ type: T.Type = T.self, withIdentifier reuseIdentifier: String = String(describing: T.self)) -> T { 74 | guard let cell = dequeueReusableCell(withIdentifier: reuseIdentifier) as? T else { 75 | fatalError("Unknown cell type (\(T.self)) for reuse identifier: \(reuseIdentifier)") 76 | } 77 | return cell 78 | } 79 | 80 | /** 81 | Dequeues a UITableViewCell for use in a UITableView. 82 | 83 | - parameter type: The type of the cell. 84 | - parameter indexPath: The index path at which to dequeue a new cell. 85 | - parameter reuseIdentifier: The reuse identifier for the cell (optional). 86 | 87 | - returns: A force-casted UITableViewCell of the specified type. 88 | 89 | By default, the class name of the cell is used as the reuse identifier. 90 | 91 | Example: 92 | ``` 93 | class CustomCell: UITableViewCell {} 94 | 95 | let tableView = UITableView() 96 | 97 | // dequeues a CustomCell class 98 | let cell = tableView.dequeueReusableCell(CustomCell.self, forIndexPath: indexPath) 99 | ``` 100 | */ 101 | public func dequeueCell(_ type: T.Type = T.self, withIdentifier reuseIdentifier: String = String(describing: T.self), for indexPath: IndexPath) -> T { 102 | guard let cell = dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as? T else { 103 | fatalError("Unknown cell type (\(T.self)) for reuse identifier: \(reuseIdentifier)") 104 | } 105 | return cell 106 | } 107 | 108 | /** 109 | Inserts rows into self. 110 | 111 | - parameter indices: The rows indices to insert into self. 112 | - parameter section: The section in which to insert the rows (optional, defaults to 0). 113 | - parameter animation: The animation to use for the row insertion (optional, defaults to `.Automatic`). 114 | */ 115 | public func insert(_ indices: [Int], section: Int = 0, animation: UITableViewRowAnimation = .automatic) { 116 | guard !indices.isEmpty else { return } 117 | 118 | let indexPaths = indices.map { IndexPath(row: $0, section: section) } 119 | 120 | beginUpdates() 121 | insertRows(at: indexPaths, with: animation) 122 | endUpdates() 123 | } 124 | 125 | /** 126 | Deletes rows from self. 127 | 128 | - parameter indices: The rows indices to delete from self. 129 | - parameter section: The section in which to delete the rows (optional, defaults to 0). 130 | - parameter animation: The animation to use for the row deletion (optional, defaults to `.Automatic`). 131 | */ 132 | public func delete(_ indices: [Int], section: Int = 0, animation: UITableViewRowAnimation = .automatic) { 133 | guard !indices.isEmpty else { return } 134 | 135 | let indexPaths = indices.map { IndexPath(row: $0, section: section) } 136 | 137 | beginUpdates() 138 | deleteRows(at: indexPaths, with: animation) 139 | endUpdates() 140 | } 141 | 142 | /** 143 | Reloads rows in self. 144 | 145 | - parameter indices: The rows indices to reload in self. 146 | - parameter section: The section in which to reload the rows (optional, defaults to 0). 147 | - parameter animation: The animation to use for reloading the rows (optional, defaults to `.Automatic`). 148 | */ 149 | public func reload(_ indices: [Int], section: Int = 0, animation: UITableViewRowAnimation = .automatic) { 150 | guard !indices.isEmpty else { return } 151 | 152 | let indexPaths = indices.map { IndexPath(row: $0, section: section) } 153 | 154 | beginUpdates() 155 | reloadRows(at: indexPaths, with: animation) 156 | endUpdates() 157 | } 158 | } 159 | 160 | // MARK: - IndexPathTraversing 161 | extension UITableView { 162 | 163 | 164 | /// The minimum ("starting") `IndexPath` for traversing a `UITableView` "sequentially". 165 | public var minimumIndexPath: IndexPath { 166 | return IndexPath(row: 0, section: 0) 167 | } 168 | 169 | /// The maximum ("ending") `IndexPath` for traversing a `UITableView` "sequentially". 170 | public var maximumIndexPath: IndexPath { 171 | let lastSection = max(0, numberOfSections - 1) 172 | let lastRow = max(0, numberOfRows(inSection: lastSection) - 1) 173 | return IndexPath(row: lastRow, section: lastSection) 174 | } 175 | 176 | 177 | /** 178 | When "sequentially" traversing a `UITableView`, what's the next `IndexPath` after the given `IndexPath`. 179 | 180 | - parameter indexPath: The current indexPath; the path we want to find what comes after. 181 | 182 | - returns: The next indexPath, or nil if we're at the maximumIndexPath 183 | - SeeAlso: `var maximumIndexpath` 184 | */ 185 | public func indexPath(after indexPath: IndexPath) -> IndexPath? { 186 | if indexPath == maximumIndexPath { 187 | return nil 188 | } 189 | 190 | assertIsValid(indexPath: indexPath) 191 | 192 | let lastRow = numberOfRows(inSection: indexPath.section) 193 | if indexPath.item == lastRow { 194 | return IndexPath(row: 0, section: indexPath.section + 1) 195 | } else { 196 | return IndexPath(row: indexPath.row + 1, section: indexPath.section) 197 | } 198 | } 199 | 200 | 201 | /** 202 | When "sequentially" traversing a `UITableView`, what's the previous `IndexPath` before the given `IndexPath`. 203 | 204 | - parameter indexPath: The current indexPath; the path we want to find what comes before. 205 | 206 | - returns: The prior indexPath, or nil if we're at the minimumIndexPath 207 | - SeeAlso: `var minimumIndexPath` 208 | */ 209 | public func indexPath(before indexPath: IndexPath) -> IndexPath? { 210 | if indexPath == minimumIndexPath { 211 | return nil 212 | } 213 | 214 | assertIsValid(indexPath: indexPath) 215 | 216 | if indexPath.item == 0 { 217 | let lastRow = numberOfRows(inSection: indexPath.section - 1) 218 | return IndexPath(row: lastRow, section: indexPath.section - 1) 219 | } else { 220 | return IndexPath(row: indexPath.row - 1, section: indexPath.section) 221 | } 222 | } 223 | 224 | private func assertIsValid(indexPath: IndexPath, file: StaticString = #file, line: UInt = #line) { 225 | let maxPath = maximumIndexPath 226 | assert( 227 | indexPath.section <= maxPath.section && indexPath.section >= 0, 228 | "Index path \(indexPath) is outside the bounds set by the minimum (\(minimumIndexPath)) and maximum (\(maxPath)) index path", 229 | file: file, 230 | line: line 231 | ) 232 | let rowCount = numberOfRows(inSection: indexPath.section) 233 | assert( 234 | indexPath.row < rowCount && indexPath.row >= 0, 235 | "Index path \(indexPath) row index is outside the bounds of the rows (\(rowCount)) in the indexPath's section", 236 | file: file, 237 | line: line 238 | ) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /Sources/UIViewController+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 4/15/15. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import UIKit 29 | 30 | extension UIViewController { 31 | 32 | /// Retrieve the view controller currently on-screen 33 | /// 34 | /// Based off code here: http://stackoverflow.com/questions/24825123/get-the-current-view-controller-from-the-app-delegate 35 | public static var current: UIViewController? { 36 | if let controller = UIApplication.shared.keyWindow?.rootViewController { 37 | return findCurrent(controller) 38 | } 39 | return nil 40 | } 41 | 42 | private static func findCurrent(_ controller: UIViewController) -> UIViewController { 43 | if let controller = controller.presentedViewController { 44 | return findCurrent(controller) 45 | } 46 | else if let controller = controller as? UISplitViewController, let lastViewController = controller.viewControllers.first, controller.viewControllers.count > 0 { 47 | return findCurrent(lastViewController) 48 | } 49 | else if let controller = controller as? UINavigationController, let topViewController = controller.topViewController, controller.viewControllers.count > 0 { 50 | return findCurrent(topViewController) 51 | } 52 | else if let controller = controller as? UITabBarController, let selectedViewController = controller.selectedViewController, (controller.viewControllers?.count ?? 0) > 0 { 53 | return findCurrent(selectedViewController) 54 | } 55 | else { 56 | return controller 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/URL+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+Extensions.swift 3 | // 4 | // Created by Jonathan Landon on 10/25/16. 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2014-2016 Oven Bits, LLC 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | import Foundation 29 | 30 | extension URL: ExpressibleByStringLiteral { 31 | /// Creates a URL initialized to the given string value. 32 | public init(stringLiteral value: StringLiteralType) { 33 | guard let url = URL(string: value) else { fatalError("Could not create URL from: \(value)") } 34 | self = url 35 | } 36 | 37 | /// Creates a URL initialized to the given value. 38 | public init(extendedGraphemeClusterLiteral value: StringLiteralType) { 39 | guard let url = URL(string: value) else { fatalError("Could not create URL from: \(value)") } 40 | self = url 41 | } 42 | 43 | /// Creates a URL initialized to the given value. 44 | public init(unicodeScalarLiteral value: StringLiteralType) { 45 | guard let url = URL(string: value) else { fatalError("Could not create URL from: \(value)") } 46 | self = url 47 | } 48 | } 49 | 50 | /** 51 | Append a path component to a url. Equivalent to `lhs.appendingPathComponent(rhs)`. 52 | 53 | - parameter lhs: The url. 54 | - parameter rhs: The path component to append. 55 | - returns: The original url with the appended path component. 56 | */ 57 | public func +(lhs: URL, rhs: String) -> URL { 58 | return lhs.appendingPathComponent(rhs) 59 | } 60 | -------------------------------------------------------------------------------- /Support/create_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo -e "Executing create_docs" 4 | 5 | if [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "master" ]; then 6 | echo -e "Generating Jazzy output \n" 7 | 8 | # Hsoi 2016-09-19 - https://github.com/realm/jazzy/issues/656 removing --swift-version to avoid problems. 9 | # jazzy --clean --author "Oven Bits, LLC" --author_url https://ovenbits.com --github_url https://github.com/ovenbits/Alexandria --xcodebuild-arguments "-scheme,Alexandria" --module Alexandria --root-url https://ovenbits.github.io/Alexandria --theme apple --swift-version 3.0 10 | jazzy --clean --author "Oven Bits, LLC" --author_url https://ovenbits.com --github_url https://github.com/ovenbits/Alexandria --xcodebuild-arguments "-scheme,Alexandria" --module Alexandria --root-url https://ovenbits.github.io/Alexandria --theme apple 11 | 12 | 13 | pushd docs 14 | 15 | echo -e "Creating gh-pages\n" 16 | git init 17 | git config user.email "travis@travis-ci.org" 18 | git config user.name "travis-ci" 19 | git add -A 20 | git commit -m "Publishing documentation from Travis build of $TRAVIS_COMMIT" 21 | git push --force --quiet "https://${GH_TOKEN}@github.com/ovenbits/Alexandria.git" master:gh-pages > /dev/null 2>&1 22 | echo -e "Published documentation to gh-pages.\n" 23 | 24 | popd 25 | fi 26 | --------------------------------------------------------------------------------