├── .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 | 
2 |
3 |
4 | # Alexandria
5 |
6 | [](https://travis-ci.org/ovenbits/Alexandria)
7 | [](https://github.com/Carthage/Carthage)
8 | 
9 | 
10 | 
11 | [](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 |
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 |
--------------------------------------------------------------------------------