├── .gitignore
├── .swift-version
├── .travis.yml
├── Example
├── Default-568h@2x.png
├── Example.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ └── contents.xcworkspacedata
└── Example
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ └── Main.storyboard
│ ├── Images.xcassets
│ ├── 0.imageset
│ │ ├── 0.png
│ │ ├── 0@2x.png
│ │ └── Contents.json
│ ├── 1.imageset
│ │ ├── 1.png
│ │ ├── 1@2x.png
│ │ └── Contents.json
│ ├── 2.imageset
│ │ ├── 2.png
│ │ ├── 2@2x.png
│ │ └── Contents.json
│ ├── 3.imageset
│ │ ├── 3.png
│ │ ├── 3@2x.png
│ │ └── Contents.json
│ ├── 4.imageset
│ │ ├── Contents.json
│ │ └── yin-yang.png
│ ├── 5.imageset
│ │ ├── Contents.json
│ │ └── Om.png
│ ├── 6.imageset
│ │ ├── Contents.json
│ │ ├── Recycling.png
│ │ └── Recycling@2x.png
│ ├── 7.imageset
│ │ ├── Contents.json
│ │ ├── twitter.png
│ │ └── twitter@2x.png
│ ├── 8.imageset
│ │ ├── 8.png
│ │ ├── 8@2x.png
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ └── satellite.imageset
│ │ ├── Contents.json
│ │ ├── satellite.png
│ │ ├── satellite@2x.png
│ │ └── satellite@3x.png
│ ├── Info.plist
│ ├── OMShadingGradientLayer
│ └── Classes
│ │ ├── Categories
│ │ ├── CALayer+AnimationKeyPath.swift
│ │ ├── CGAffineTransform+Extensions.swift
│ │ ├── CGColorSpace+Extension.swift
│ │ ├── CGFloat+Extensions.swift
│ │ ├── CGFloat+Math.swift
│ │ ├── CGPoint+Extension.swift
│ │ ├── CGSize+Math.swift
│ │ ├── Double+Math.swift
│ │ ├── Float+Extensions.swift
│ │ ├── Float+Math.swift
│ │ ├── UIColor+Extensions.swift
│ │ └── UIColor+Interpolation.swift
│ │ ├── Math
│ │ ├── Easing.swift
│ │ ├── Interpolation.swift
│ │ └── Math.swift
│ │ ├── OMGradientLayer.swift
│ │ ├── OMGradientLayerProtocols.swift
│ │ ├── OMShadingGradient.swift
│ │ └── OMShadingGradientLayer.swift
│ └── ViewController.swift
├── LICENSE
├── OMCircularProgress.podspec
├── OMCircularProgress
└── Classes
│ ├── Extensions
│ ├── CALayer+AnimationKeyPath.swift
│ ├── CALayer+Extensions.swift
│ ├── CAShapeLayer+HitTest.swift
│ ├── CATransform3D+Extension.swift
│ ├── CGAffineTransform+Extension.swift
│ ├── CGAffineTransform+Extensions.swift
│ ├── CGColorSpace+Extension.swift
│ ├── CGFloat+Extensions.swift
│ ├── CGFloat+Math.swift
│ ├── CGPoint+Center.swift
│ ├── CGPoint+Extension.swift
│ ├── CGRect+Extensions.swift
│ ├── CGSize+Math.swift
│ ├── Compatibility.swift
│ ├── Double+Math.swift
│ ├── Float+Extensions.swift
│ ├── Float+Math.swift
│ ├── Interpolation.swift
│ ├── NSNumber+Format.swift
│ ├── String+Random.swift
│ ├── UIBezierPath+Polygon.swift
│ ├── UIBezierPath+Subpaths.swift
│ ├── UIColor+Crayola.swift
│ ├── UIColor+Extensions.swift
│ ├── UIColor+Interpolation.swift
│ ├── UIFont+Extensions.swift
│ └── UIImage+Extensions.swift
│ ├── Log.swift
│ ├── OMCircularProgress.swift
│ └── OMLayers
│ ├── OMNumberLayer
│ └── OMNumberLayer.swift
│ ├── OMProgressImageLayer
│ └── OMProgressImageLayer.swift
│ └── OMTextLayer
│ └── OMTextLayer.swift
├── README.md
├── ScreenShot
└── ScreenShot.png
└── gif
└── gif.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.moved-aside
15 | DerivedData
16 | *.hmap
17 | *.ipa
18 | *.xcuserstate
19 |
20 | # CocoaPods
21 | #
22 | # We recommend against adding the Pods directory to your .gitignore. However
23 | # you should judge for yourself, the pros and cons are mentioned at:
24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
25 | #
26 | # Pods/
27 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 3.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 |
2 |
3 | language: swift
4 | osx_image: xcode7.3
5 | branches:
6 | only:
7 | - master
8 | env:
9 | - LC_CTYPE=en_US.UTF-8 LANG=en_US.UTF-8
10 | script:
11 | - set -o pipefail
12 | before_script:
13 | - gem install xcpretty
14 |
15 | script:
16 | - xcodebuild -project ExampleSwift/ExampleSwift.xcodeproj -scheme ExampleSwift -sdk iphonesimulator test | xcpretty -c
17 | - pod lib lint --quick
--------------------------------------------------------------------------------
/Example/Default-568h@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Default-568h@2x.png
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | //
18 | // AppDelegate.swift
19 | // ExampleSwift
20 | //
21 | // Created by Jorge Ouahbi on 19/1/15.
22 | //
23 |
24 | import UIKit
25 |
26 | @UIApplicationMain
27 | class AppDelegate: UIResponder, UIApplicationDelegate {
28 |
29 | var window: UIWindow?
30 |
31 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
32 | // Override point for customization after application launch.
33 | return true
34 | }
35 |
36 | func applicationWillResignActive(_ application: UIApplication) {
37 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
38 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
39 | }
40 |
41 | func applicationDidEnterBackground(_ application: UIApplication) {
42 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
43 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
44 | }
45 |
46 | func applicationWillEnterForeground(_ application: UIApplication) {
47 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
48 | }
49 |
50 | func applicationDidBecomeActive(_ application: UIApplication) {
51 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
52 | }
53 |
54 | func applicationWillTerminate(_ application: UIApplication) {
55 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
56 | }
57 |
58 |
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/Example/Example/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/0.imageset/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/0.imageset/0.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/0.imageset/0@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/0.imageset/0@2x.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/0.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "iphone",
9 | "filename" : "0.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "iphone",
14 | "scale" : "3x"
15 | },
16 | {
17 | "idiom" : "ipad",
18 | "scale" : "1x"
19 | },
20 | {
21 | "idiom" : "ipad",
22 | "filename" : "0@2x.png",
23 | "scale" : "2x"
24 | }
25 | ],
26 | "info" : {
27 | "version" : 1,
28 | "author" : "xcode"
29 | }
30 | }
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/1.imageset/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/1.imageset/1.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/1.imageset/1@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/1.imageset/1@2x.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "iphone",
9 | "filename" : "1.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "iphone",
14 | "scale" : "3x"
15 | },
16 | {
17 | "idiom" : "ipad",
18 | "scale" : "1x"
19 | },
20 | {
21 | "idiom" : "ipad",
22 | "filename" : "1@2x.png",
23 | "scale" : "2x"
24 | }
25 | ],
26 | "info" : {
27 | "version" : 1,
28 | "author" : "xcode"
29 | }
30 | }
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/2.imageset/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/2.imageset/2.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/2.imageset/2@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/2.imageset/2@2x.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "iphone",
9 | "filename" : "2.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "iphone",
14 | "scale" : "3x"
15 | },
16 | {
17 | "idiom" : "ipad",
18 | "scale" : "1x"
19 | },
20 | {
21 | "idiom" : "ipad",
22 | "filename" : "2@2x.png",
23 | "scale" : "2x"
24 | }
25 | ],
26 | "info" : {
27 | "version" : 1,
28 | "author" : "xcode"
29 | }
30 | }
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/3.imageset/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/3.imageset/3.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/3.imageset/3@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/3.imageset/3@2x.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/3.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "iphone",
9 | "filename" : "3.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "iphone",
14 | "scale" : "3x"
15 | },
16 | {
17 | "idiom" : "ipad",
18 | "scale" : "1x"
19 | },
20 | {
21 | "idiom" : "ipad",
22 | "filename" : "3@2x.png",
23 | "scale" : "2x"
24 | }
25 | ],
26 | "info" : {
27 | "version" : 1,
28 | "author" : "xcode"
29 | }
30 | }
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/4.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "yin-yang.png",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/4.imageset/yin-yang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/4.imageset/yin-yang.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/5.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Om.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/5.imageset/Om.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/5.imageset/Om.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/6.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "iphone",
9 | "filename" : "Recycling.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "iphone",
14 | "scale" : "3x"
15 | },
16 | {
17 | "idiom" : "ipad",
18 | "filename" : "Recycling@2x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "idiom" : "ipad",
23 | "scale" : "2x"
24 | }
25 | ],
26 | "info" : {
27 | "version" : 1,
28 | "author" : "xcode"
29 | }
30 | }
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/6.imageset/Recycling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/6.imageset/Recycling.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/6.imageset/Recycling@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/6.imageset/Recycling@2x.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/7.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "iphone",
9 | "filename" : "twitter.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "iphone",
14 | "scale" : "3x"
15 | },
16 | {
17 | "idiom" : "ipad",
18 | "scale" : "1x"
19 | },
20 | {
21 | "idiom" : "ipad",
22 | "filename" : "twitter@2x.png",
23 | "scale" : "2x"
24 | }
25 | ],
26 | "info" : {
27 | "version" : 1,
28 | "author" : "xcode"
29 | }
30 | }
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/7.imageset/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/7.imageset/twitter.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/7.imageset/twitter@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/7.imageset/twitter@2x.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/8.imageset/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/8.imageset/8.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/8.imageset/8@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/8.imageset/8@2x.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/8.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "iphone",
9 | "filename" : "8.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "iphone",
14 | "scale" : "3x"
15 | },
16 | {
17 | "idiom" : "ipad",
18 | "filename" : "8@2x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "idiom" : "ipad",
23 | "scale" : "2x"
24 | }
25 | ],
26 | "info" : {
27 | "version" : 1,
28 | "author" : "xcode"
29 | }
30 | }
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "29x29",
26 | "scale" : "3x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "40x40",
36 | "scale" : "3x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "57x57",
41 | "scale" : "1x"
42 | },
43 | {
44 | "idiom" : "iphone",
45 | "size" : "57x57",
46 | "scale" : "2x"
47 | },
48 | {
49 | "idiom" : "iphone",
50 | "size" : "60x60",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "iphone",
55 | "size" : "60x60",
56 | "scale" : "3x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "20x20",
61 | "scale" : "1x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "20x20",
66 | "scale" : "2x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "29x29",
71 | "scale" : "1x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "29x29",
76 | "scale" : "2x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "40x40",
81 | "scale" : "1x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "40x40",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ipad",
90 | "size" : "50x50",
91 | "scale" : "1x"
92 | },
93 | {
94 | "idiom" : "ipad",
95 | "size" : "50x50",
96 | "scale" : "2x"
97 | },
98 | {
99 | "idiom" : "ipad",
100 | "size" : "72x72",
101 | "scale" : "1x"
102 | },
103 | {
104 | "idiom" : "ipad",
105 | "size" : "72x72",
106 | "scale" : "2x"
107 | },
108 | {
109 | "idiom" : "ipad",
110 | "size" : "76x76",
111 | "scale" : "1x"
112 | },
113 | {
114 | "idiom" : "ipad",
115 | "size" : "76x76",
116 | "scale" : "2x"
117 | },
118 | {
119 | "idiom" : "ipad",
120 | "size" : "83.5x83.5",
121 | "scale" : "2x"
122 | },
123 | {
124 | "idiom" : "ios-marketing",
125 | "size" : "1024x1024",
126 | "scale" : "1x"
127 | },
128 | {
129 | "size" : "24x24",
130 | "idiom" : "watch",
131 | "scale" : "2x",
132 | "role" : "notificationCenter",
133 | "subtype" : "38mm"
134 | },
135 | {
136 | "size" : "27.5x27.5",
137 | "idiom" : "watch",
138 | "scale" : "2x",
139 | "role" : "notificationCenter",
140 | "subtype" : "42mm"
141 | },
142 | {
143 | "size" : "29x29",
144 | "idiom" : "watch",
145 | "role" : "companionSettings",
146 | "scale" : "2x"
147 | },
148 | {
149 | "size" : "29x29",
150 | "idiom" : "watch",
151 | "role" : "companionSettings",
152 | "scale" : "3x"
153 | },
154 | {
155 | "size" : "40x40",
156 | "idiom" : "watch",
157 | "scale" : "2x",
158 | "role" : "appLauncher",
159 | "subtype" : "38mm"
160 | },
161 | {
162 | "size" : "44x44",
163 | "idiom" : "watch",
164 | "scale" : "2x",
165 | "role" : "appLauncher",
166 | "subtype" : "40mm"
167 | },
168 | {
169 | "size" : "50x50",
170 | "idiom" : "watch",
171 | "scale" : "2x",
172 | "role" : "appLauncher",
173 | "subtype" : "44mm"
174 | },
175 | {
176 | "size" : "86x86",
177 | "idiom" : "watch",
178 | "scale" : "2x",
179 | "role" : "quickLook",
180 | "subtype" : "38mm"
181 | },
182 | {
183 | "size" : "98x98",
184 | "idiom" : "watch",
185 | "scale" : "2x",
186 | "role" : "quickLook",
187 | "subtype" : "42mm"
188 | },
189 | {
190 | "size" : "108x108",
191 | "idiom" : "watch",
192 | "scale" : "2x",
193 | "role" : "quickLook",
194 | "subtype" : "44mm"
195 | },
196 | {
197 | "idiom" : "watch-marketing",
198 | "size" : "1024x1024",
199 | "scale" : "1x"
200 | },
201 | {
202 | "idiom" : "car",
203 | "size" : "120x120",
204 | "scale" : "1x"
205 | },
206 | {
207 | "size" : "44x44",
208 | "idiom" : "watch",
209 | "scale" : "2x",
210 | "role" : "longLook",
211 | "subtype" : "42mm"
212 | },
213 | {
214 | "scale" : "2x",
215 | "idiom" : "watch",
216 | "unassigned" : true,
217 | "role" : "notificationCenter",
218 | "subtype" : "38mm"
219 | },
220 | {
221 | "scale" : "2x",
222 | "idiom" : "watch",
223 | "unassigned" : true,
224 | "role" : "notificationCenter",
225 | "subtype" : "38mm"
226 | },
227 | {
228 | "scale" : "2x",
229 | "idiom" : "watch",
230 | "unassigned" : true,
231 | "role" : "notificationCenter",
232 | "subtype" : "42mm"
233 | },
234 | {
235 | "idiom" : "watch",
236 | "unassigned" : true,
237 | "role" : "companionSettings",
238 | "scale" : "3x"
239 | },
240 | {
241 | "size" : "44x44",
242 | "idiom" : "watch",
243 | "scale" : "2x",
244 | "unassigned" : true,
245 | "role" : "appLauncher",
246 | "subtype" : "42mm"
247 | }
248 | ],
249 | "info" : {
250 | "version" : 1,
251 | "author" : "xcode"
252 | }
253 | }
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/satellite.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "filename" : "satellite.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "filename" : "satellite@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "3x"
16 | },
17 | {
18 | "idiom" : "ipad",
19 | "scale" : "1x"
20 | },
21 | {
22 | "idiom" : "ipad",
23 | "filename" : "satellite@3x.png",
24 | "scale" : "2x"
25 | }
26 | ],
27 | "info" : {
28 | "version" : 1,
29 | "author" : "xcode"
30 | }
31 | }
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/satellite.imageset/satellite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/satellite.imageset/satellite.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/satellite.imageset/satellite@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/satellite.imageset/satellite@2x.png
--------------------------------------------------------------------------------
/Example/Example/Images.xcassets/satellite.imageset/satellite@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/Example/Example/Images.xcassets/satellite.imageset/satellite@3x.png
--------------------------------------------------------------------------------
/Example/Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 4
23 | LSRequiresIPhoneOS
24 |
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Categories/CALayer+AnimationKeyPath.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 |
18 | //
19 | // CALayer+AnimationKeyPath.swift
20 | //
21 | // Created by Jorge Ouahbi on 26/3/15.
22 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
23 | //
24 | // v1.0
25 |
26 | import UIKit
27 | public extension CALayer {
28 |
29 | // MARK: - CALayer Animation Helpers
30 |
31 | func animationActionForKey(_ event:String!) -> CABasicAnimation! {
32 | let animation = CABasicAnimation(keyPath: event)
33 | //animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunction.linear)
34 | animation.fromValue = self.presentation()!.value(forKey: event);
35 | return animation
36 | }
37 |
38 | func animateKeyPath(_ keyPath : String,
39 | fromValue : AnyObject?,
40 | toValue:AnyObject?,
41 | beginTime:TimeInterval,
42 | duration:TimeInterval,
43 | delegate:AnyObject?)
44 | {
45 | let animation = CABasicAnimation(keyPath:keyPath);
46 |
47 | var currentValue: AnyObject? = self.presentation()?.value(forKey: keyPath) as AnyObject?
48 |
49 | if (currentValue == nil) {
50 | currentValue = fromValue
51 | }
52 |
53 | animation.fromValue = currentValue
54 | animation.toValue = toValue
55 | animation.delegate = delegate as! CAAnimationDelegate?
56 |
57 | if(duration > 0.0){
58 | animation.duration = duration
59 | }
60 | if(beginTime > 0.0){
61 | animation.beginTime = beginTime
62 | }
63 |
64 | //animation.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.linear)
65 | animation.setValue(self,forKey:keyPath)
66 | self.add(animation, forKey:keyPath)
67 | self.setValue(toValue,forKey:keyPath)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Categories/CGAffineTransform+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGAffineTransform+Extensions.swift
3 | // ExampleSwift
4 | //
5 | // Created by Jorge Ouahbi on 8/10/16.
6 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public extension CGAffineTransform {
12 | static func randomRotate() -> CGAffineTransform {
13 | return CGAffineTransform(rotationAngle: CGFloat((drand48() * 360.0).degreesToRadians()))
14 | }
15 | static func randomScale(scaleXMax:CGFloat = 16,scaleYMax:CGFloat = 16) -> CGAffineTransform {
16 | let scaleX = (CGFloat(drand48()) * scaleXMax) + 1.0
17 | let scaleY = (CGFloat(drand48()) * scaleYMax) + 1.0
18 |
19 | let flip:CGFloat = drand48() < 0.5 ? -1 : 1
20 | return CGAffineTransform(scaleX: scaleX, y:scaleY * flip)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Categories/CGColorSpace+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 |
18 | //
19 | // CGColorSpace+Extensions.swift
20 | //
21 | // Created by Jorge Ouahbi on 13/5/16.
22 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
23 | //
24 | // v 1.0
25 |
26 | import UIKit
27 |
28 | extension CGColorSpaceModel {
29 | var name : String {
30 | switch self {
31 | case .unknown:return "Unknown"
32 | case .monochrome:return "Monochrome"
33 | case .rgb:return "RGB"
34 | case .cmyk:return "CMYK"
35 | case .lab:return "Lab"
36 | case .deviceN:return "DeviceN"
37 | case .indexed:return "Indexed"
38 | case .pattern:return "Pattern"
39 | case .XYZ:return "XYZ"
40 | @unknown default:
41 | return "Unknown"
42 | }
43 | }
44 | }
45 | extension CGColorSpace {
46 | var isUnknown: Bool {
47 | return model == .unknown
48 | }
49 | var isRGB : Bool {
50 | return model == .rgb
51 | }
52 | var isCMYK : Bool {
53 | return model == .cmyk
54 | }
55 | var isLab : Bool {
56 | return model == .lab
57 | }
58 | var isMonochrome : Bool {
59 | return model == .monochrome
60 | }
61 | var isDeviceN : Bool {
62 | return model == .deviceN
63 | }
64 | var isIndexed : Bool {
65 | return model == .indexed
66 | }
67 | var isPattern : Bool {
68 | return model == .pattern
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Categories/CGFloat+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGFloat+Extensions.swift
3 | //
4 | // Created by Jorge Ouahbi on 19/8/16.
5 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
6 | //
7 | // v1.0
8 |
9 | import UIKit
10 |
11 |
12 | extension CGFloat {
13 | func format(_ short:Bool)->String {
14 | if(short){
15 | return String(format: "%.1f", self)
16 | }else{
17 | return String(format: "%.6f", self)
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Categories/CGFloat+Math.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 |
18 | // CGFloat+Math.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/11/15.
21 | // Copyright d© 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 | /// CGFloat Extension for conversion from/to degrees/radians and clamp
27 |
28 | public func clamp(_ value:CGFloat,lowerValue: CGFloat, upperValue: CGFloat) -> CGFloat{
29 | return Swift.min(Swift.max(value, lowerValue), upperValue)
30 | }
31 |
32 | public func map(input:CGFloat,input_start:CGFloat,input_end:CGFloat,output_start:CGFloat,output_end:CGFloat)-> CGFloat {
33 | let slope = 1.0 * (output_end - output_start) / (input_end - input_start)
34 | return output_start + round(slope * (input - input_start))
35 | }
36 |
37 |
38 | public extension CGFloat {
39 |
40 | func degreesToRadians () -> CGFloat {
41 | return self * CGFloat(0.01745329252)
42 | }
43 | func radiansToDegrees () -> CGFloat {
44 | return self * CGFloat(57.29577951)
45 | }
46 | mutating func clamp(toLowerValue lowerValue: CGFloat, upperValue: CGFloat){
47 | self = Swift.min(Swift.max(self, lowerValue), upperValue)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Categories/CGPoint+Extension.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright 2015 - Jorge Ouahbi
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 |
18 | //
19 | // CGPoint+Extension.swift
20 | //
21 | // Created by Jorge Ouahbi on 26/4/16.
22 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
23 | //
24 |
25 | // v1.0
26 |
27 | import UIKit
28 |
29 |
30 | public func ==(lhs: CGPoint, rhs: CGPoint) -> Bool {
31 | return lhs.equalTo(rhs)
32 | }
33 |
34 | public func *(lhs: CGPoint, rhs: CGSize) -> CGPoint {
35 | return CGPoint(x:lhs.x*rhs.width,y: lhs.y*rhs.height)
36 | }
37 |
38 | public func *(lhs: CGPoint, scalar: CGFloat) -> CGPoint {
39 | return CGPoint(x:lhs.x*scalar,y: lhs.y*scalar)
40 | }
41 | public func /(lhs: CGPoint, rhs: CGSize) -> CGPoint {
42 | return CGPoint(x:lhs.x/rhs.width,y: lhs.y/rhs.height)
43 | }
44 |
45 |
46 | extension CGPoint : Hashable {
47 |
48 | public var hashValue: Int {
49 | return self.x.hashValue << MemoryLayout.size ^ self.y.hashValue
50 |
51 | }
52 | var isZero : Bool {
53 | return self.equalTo(CGPoint.zero)
54 | }
55 |
56 | func distance(_ point:CGPoint) -> CGFloat {
57 | let diff = CGPoint(x: self.x - point.x, y: self.y - point.y);
58 | return CGFloat(sqrtf(Float(diff.x*diff.x + diff.y*diff.y)));
59 | }
60 |
61 |
62 | func projectLine( _ point:CGPoint, length:CGFloat) -> CGPoint {
63 |
64 | var newPoint = CGPoint(x: point.x, y: point.y)
65 | let x = (point.x - self.x);
66 | let y = (point.y - self.y);
67 | if (x.floatingPointClass == .negativeZero) {
68 | newPoint.y += length;
69 | } else if (y.floatingPointClass == .negativeZero) {
70 | newPoint.x += length;
71 | } else {
72 | #if CGFLOAT_IS_DOUBLE
73 | let angle = atan(y / x);
74 | newPoint.x += sin(angle) * length;
75 | newPoint.y += cos(angle) * length;
76 | #else
77 | let angle = atanf(Float(y) / Float(x));
78 | newPoint.x += CGFloat(sinf(angle) * Float(length));
79 | newPoint.y += CGFloat(cosf(angle) * Float(length));
80 | #endif
81 | }
82 | return newPoint;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Categories/CGSize+Math.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | //
18 | // CGSizeExtension.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/11/15.
21 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 | /**
27 | * @brief CGSize Extension
28 | */
29 | extension CGSize
30 | {
31 | func min() -> CGFloat {
32 | return Swift.min(height,width);
33 | }
34 |
35 | func max() -> CGFloat {
36 | return Swift.max(height,width);
37 | }
38 |
39 | func max(_ other : CGSize) -> CGSize {
40 | return self.max() >= other.max() ? self : other;
41 | }
42 |
43 | func hypot() -> CGFloat {
44 | return CoreGraphics.hypot(height,width)
45 | }
46 |
47 | func center() -> CGPoint {
48 | return CGPoint(x:width * 0.5,y:height * 0.5)
49 | }
50 |
51 | func integral() -> CGSize {
52 | return CGSize(width:round(width),height:round(height))
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Categories/Double+Math.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 |
18 | // Double+Math.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/11/15.
21 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 | /**
27 | * Double Extension for conversion from/to degrees/radians and clamp
28 | */
29 |
30 | public extension Double {
31 |
32 | func degreesToRadians () -> Double {
33 | return self * 0.01745329252
34 | }
35 | func radiansToDegrees () -> Double {
36 | return self * 57.29577951
37 | }
38 |
39 | mutating func clamp(toLowerValue lowerValue: Double, upperValue: Double){
40 | self = min(max(self, lowerValue), upperValue)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Categories/Float+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Float+Extensions.swift
3 | //
4 | // Created by Jorge Ouahbi on 19/8/16.
5 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
6 | //
7 | // v1.0
8 |
9 | import UIKit
10 |
11 |
12 | extension Float {
13 | func format(_ short:Bool)->String {
14 | return CGFloat(self).format(short)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Categories/Float+Math.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 |
18 | // Float+Math.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/11/15.
21 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 | /**
27 | * Float Extension for conversion from/to degrees/radians and clamp
28 | */
29 |
30 | public extension Float {
31 |
32 | func degreesToRadians () -> Float {
33 | return self * 0.01745329252
34 | }
35 | func radiansToDegrees () -> Float {
36 | return self * 57.29577951
37 | }
38 |
39 | mutating func clamp(toLowerValue lowerValue: Float, upperValue: Float){
40 | self = min(max(self, lowerValue), upperValue)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Categories/UIColor+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 |
18 | //
19 | // UIColor+Extensions.swift
20 | //
21 | // Created by Jorge Ouahbi on 27/4/16.
22 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
23 | //
24 | // v 1.0 Merged files
25 | // v 1.1 Some clean and better component access
26 |
27 |
28 | import UIKit
29 |
30 | /// Attributes
31 |
32 | let kLuminanceDarkCutoff:CGFloat = 0.6;
33 |
34 | extension UIColor
35 | {
36 | /// chroma RGB
37 | var croma : CGFloat {
38 |
39 | let comp = components
40 | let min1 = min(comp[1], comp[2])
41 | let max1 = max(comp[1], comp[2])
42 |
43 | return max(comp[0], max1) - min(comp[0], min1);
44 |
45 | }
46 | // luma RGB
47 | // WebKit
48 | // luma = (r * 0.2125 + g * 0.7154 + b * 0.0721) * ((double)a / 255.0);
49 |
50 | var luma : CGFloat {
51 | let comp = components
52 | let lumaRed = 0.2126 * Float(comp[0])
53 | let lumaGreen = 0.7152 * Float(comp[1])
54 | let lumaBlue = 0.0722 * Float(comp[2])
55 | let luma = Float(lumaRed + lumaGreen + lumaBlue)
56 |
57 | return CGFloat(luma * Float(components[3]))
58 | }
59 |
60 | var luminance : CGFloat {
61 | let comp = components
62 | let fmin = min(min(comp[0],comp[1]),comp[2])
63 | let fmax = max(max(comp[0],comp[1]),comp[2])
64 | return (fmax + fmin) / 2.0;
65 | }
66 |
67 |
68 | var isLight : Bool {
69 | return self.luma >= kLuminanceDarkCutoff
70 | }
71 |
72 | var isDark : Bool {
73 | return self.luma < kLuminanceDarkCutoff;
74 | }
75 | }
76 |
77 |
78 | extension UIColor {
79 |
80 | public convenience init(hex: String) {
81 | var characterSet = NSCharacterSet.whitespacesAndNewlines
82 | characterSet = characterSet.union(NSCharacterSet(charactersIn: "#") as CharacterSet)
83 | let cString = hex.trimmingCharacters(in: characterSet).uppercased()
84 | if (cString.count != 6) {
85 | self.init(white: 1.0, alpha: 1.0)
86 | } else {
87 | var rgbValue: UInt32 = 0
88 | Scanner(string: cString).scanHexInt32(&rgbValue)
89 |
90 | self.init(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
91 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
92 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
93 | alpha: CGFloat(1.0))
94 | }
95 | }
96 |
97 | // MARK: RGBA components
98 |
99 | /// Returns an array of `CGFloat`s containing four elements with `self`'s:
100 | /// - red (index `0`)
101 | /// - green (index `1`)
102 | /// - blue (index `2`)
103 | /// - alpha (index `3`)
104 | /// or
105 | /// - white (index `0`)
106 | /// - alpha (index `1`)
107 | var components : [CGFloat] {
108 | // Constructs the array in which to store the RGBA-components.
109 | if let components = self.cgColor.components {
110 | if numberOfComponents == 4 {
111 | return [components[0],components[1],components[2],components[3]]
112 | } else {
113 | return [components[0], components[1]]
114 | }
115 | }
116 |
117 | return []
118 | }
119 |
120 | /// red component
121 | var r : CGFloat {
122 | return components[0];
123 | }
124 | /// green component
125 | var g: CGFloat {
126 | return components[1];
127 | }
128 | /// blue component
129 | var b: CGFloat {
130 | return components[2];
131 | }
132 |
133 | /// number of color components
134 | var numberOfComponents : size_t {
135 | return self.cgColor.numberOfComponents
136 | }
137 | /// color space
138 | var colorSpace : CGColorSpace? {
139 | return self.cgColor.colorSpace
140 | }
141 |
142 | // MARK: HSBA components
143 |
144 | /// Returns an array of `CGFloat`s containing four elements with `self`'s:
145 | /// - hue (index `0`)
146 | /// - saturation (index `1`)
147 | /// - brightness (index `2`)
148 | /// - alpha (index `3`)
149 | var hsbaComponents: [CGFloat] {
150 | // Constructs the array in which to store the HSBA-components.
151 |
152 | var components0:CGFloat = 0
153 | var components1:CGFloat = 0
154 | var components2:CGFloat = 0
155 | var components3:CGFloat = 0
156 |
157 | getHue( &components0,
158 | saturation: &components1,
159 | brightness: &components2,
160 | alpha: &components3)
161 |
162 | return [components0,components1,components2,components3];
163 | }
164 | /// alpha component
165 | var alpha : CGFloat {
166 | return self.cgColor.alpha
167 | }
168 | /// hue component
169 | var hue: CGFloat {
170 | return hsbaComponents[0];
171 | }
172 | /// saturation component
173 | var saturation: CGFloat {
174 | return hsbaComponents[1];
175 | }
176 |
177 | /// Returns a lighter color by the provided percentage
178 | ///
179 | /// - param: lighting percent percentage
180 | /// - returns: lighter UIColor
181 |
182 | func lighterColor(percent : Double) -> UIColor {
183 | return colorWithBrightnessFactor(factor: CGFloat(1 + percent));
184 | }
185 |
186 | /// Returns a darker color by the provided percentage
187 | ///
188 | /// - param: darking percent percentage
189 | /// - returns: darker UIColor
190 |
191 | func darkerColor(percent : Double) -> UIColor {
192 | return colorWithBrightnessFactor(factor: CGFloat(1 - percent));
193 | }
194 |
195 | /// Return a modified color using the brightness factor provided
196 | ///
197 | /// - param: factor brightness factor
198 | /// - returns: modified color
199 | func colorWithBrightnessFactor(factor: CGFloat) -> UIColor {
200 | return UIColor(hue: hsbaComponents[0],
201 | saturation: hsbaComponents[1],
202 | brightness: hsbaComponents[2] * factor,
203 | alpha: hsbaComponents[3])
204 |
205 | }
206 |
207 | /// Color difference
208 | ///
209 | /// - Parameter fromColor: color
210 | /// - Returns: Color difference
211 | func difference(fromColor: UIColor) -> Int {
212 | // get the current color's red, green, blue and alpha values
213 | let red:CGFloat = self.components[0]
214 | let green:CGFloat = self.components[1]
215 | let blue:CGFloat = self.components[2]
216 | //var alpha:CGFloat = self.components[3]
217 |
218 | // get the fromColor's red, green, blue and alpha values
219 | let fromRed:CGFloat = fromColor.components[0]
220 | let fromGreen:CGFloat = fromColor.components[1]
221 | let fromBlue:CGFloat = fromColor.components[2]
222 | //var fromAlpha:CGFloat = fromColor.components[3]
223 |
224 | let redValue = (max(red, fromRed) - min(red, fromRed)) * 255
225 | let greenValue = (max(green, fromGreen) - min(green, fromGreen)) * 255
226 | let blueValue = (max(blue, fromBlue) - min(blue, fromBlue)) * 255
227 |
228 | return Int(redValue + greenValue + blueValue)
229 | }
230 |
231 | /// Brightness difference
232 | ///
233 | /// - Parameter fromColor: color
234 | /// - Returns: Brightness difference
235 | func brightnessDifference(fromColor: UIColor) -> Int {
236 | // get the current color's red, green, blue and alpha values
237 | let red:CGFloat = self.components[0]
238 | let green:CGFloat = self.components[1]
239 | let blue:CGFloat = self.components[2]
240 | //var alpha:CGFloat = self.components[3]
241 | let brightness = Int((((red * 299) + (green * 587) + (blue * 114)) * 255) / 1000)
242 |
243 | // get the fromColor's red, green, blue and alpha values
244 | let fromRed:CGFloat = fromColor.components[0]
245 | let fromGreen:CGFloat = fromColor.components[1]
246 | let fromBlue:CGFloat = fromColor.components[2]
247 | //var fromAlpha:CGFloat = fromColor.components[3]
248 |
249 | let fromBrightness = Int((((fromRed * 299) + (fromGreen * 587) + (fromBlue * 114)) * 255) / 1000)
250 |
251 | return max(brightness, fromBrightness) - min(brightness, fromBrightness)
252 | }
253 |
254 | /// Color delta
255 | ///
256 | /// - Parameter color: color
257 | /// - Returns: Color delta
258 | func colorDelta (color: UIColor) -> Double {
259 | var total = CGFloat(0)
260 | total += pow(self.components[0] - color.components[0], 2)
261 | total += pow(self.components[1] - color.components[1], 2)
262 | total += pow(self.components[2] - color.components[2], 2)
263 | total += pow(self.components[3] - color.components[3], 2)
264 | return sqrt(Double(total) * 255.0)
265 | }
266 |
267 | /// Short UIColor description
268 | var shortDescription:String {
269 | let components = self.components
270 | if (numberOfComponents == 2) {
271 | let c = String(format: "%.1f %.1f", components[0], components[1])
272 | if let colorSpace = self.colorSpace {
273 | return "\(colorSpace.model.name):\(c)";
274 | }
275 | return "\(c)";
276 | } else {
277 | assert(numberOfComponents == 4)
278 | let c = String(format: "%.1f %.1f %.1f %.1f", components[0],components[1],components[2],components[3])
279 | if let colorSpace = self.colorSpace {
280 | return "\(colorSpace.model.name):\(c)";
281 | }
282 | return "\(c)";
283 | }
284 | }
285 | }
286 |
287 | /// Random RGBA
288 |
289 | extension UIColor {
290 |
291 | class public func random() -> UIColor? {
292 | let r = CGFloat(drand48())
293 | let g = CGFloat(drand48())
294 | let b = CGFloat(drand48())
295 | return UIColor(red: r, green: g, blue: b, alpha: 1.0)
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Categories/UIColor+Interpolation.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright 2015 - Jorge Ouahbi
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | //
18 |
19 | //
20 | // UIColor+Interpolation.swift
21 | //
22 | // Created by Jorge Ouahbi on 27/4/16.
23 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
24 | //
25 |
26 |
27 | import UIKit
28 |
29 | extension UIColor
30 | {
31 | // RGBA
32 |
33 | /// Linear interpolation
34 | ///
35 | /// - Parameters:
36 | /// - start: start UIColor
37 | /// - end: start UIColor
38 | /// - t: alpha
39 | /// - Returns: return UIColor
40 |
41 | public class func lerp(_ start:UIColor, end:UIColor, t:CGFloat) -> UIColor {
42 |
43 | let srgba = start.components
44 | let ergba = end.components
45 |
46 | return UIColor(red: Interpolation.lerp(srgba[0],y1: ergba[0],t: t),
47 | green: Interpolation.lerp(srgba[1],y1: ergba[1],t: t),
48 | blue: Interpolation.lerp(srgba[2],y1: ergba[2],t: t),
49 | alpha: Interpolation.lerp(srgba[3],y1: ergba[3],t: t))
50 | }
51 |
52 | /// Cosine interpolate
53 | ///
54 | /// - Parameters:
55 | /// - start: start UIColor
56 | /// - end: start UIColor
57 | /// - t: alpha
58 | /// - Returns: return UIColor
59 | public class func coserp(_ start:UIColor, end:UIColor, t:CGFloat) -> UIColor {
60 | let srgba = start.components
61 | let ergba = end.components
62 | return UIColor(red: Interpolation.coserp(srgba[0],y1: ergba[0],t: t),
63 | green: Interpolation.coserp(srgba[1],y1: ergba[1],t: t),
64 | blue: Interpolation.coserp(srgba[2],y1: ergba[2],t: t),
65 | alpha: Interpolation.coserp(srgba[3],y1: ergba[3],t: t))
66 | }
67 |
68 | /// Exponential interpolation
69 | ///
70 | /// - Parameters:
71 | /// - start: start UIColor
72 | /// - end: start UIColor
73 | /// - t: alpha
74 | /// - Returns: return UIColor
75 |
76 | public class func eerp(_ start:UIColor, end:UIColor, t:CGFloat) -> UIColor {
77 | let srgba = start.components
78 | let ergba = end.components
79 |
80 | let r = clamp(Interpolation.eerp(srgba[0],y1: ergba[0],t: t), lowerValue: 0,upperValue: 1)
81 | let g = clamp(Interpolation.eerp(srgba[1],y1: ergba[1],t: t),lowerValue: 0, upperValue: 1)
82 | let b = clamp(Interpolation.eerp(srgba[2],y1: ergba[2],t: t), lowerValue: 0, upperValue: 1)
83 | let a = clamp(Interpolation.eerp(srgba[3],y1: ergba[3],t: t), lowerValue: 0,upperValue: 1)
84 |
85 | assert(r <= 1.0 && g <= 1.0 && b <= 1.0 && a <= 1.0);
86 |
87 | return UIColor(red: r,
88 | green: g,
89 | blue: b,
90 | alpha: a)
91 |
92 | }
93 |
94 |
95 | /// Bilinear interpolation
96 | ///
97 | /// - Parameters:
98 | /// - start: start UIColor
99 | /// - end: start UIColor
100 | /// - t: alpha
101 | /// - Returns: return UIColor
102 |
103 | public class func bilerp(_ start:[UIColor], end:[UIColor], t:[CGFloat]) -> UIColor {
104 | let srgba0 = start[0].components
105 | let ergba0 = end[0].components
106 |
107 | let srgba1 = start[1].components
108 | let ergba1 = end[1].components
109 |
110 | return UIColor(red: Interpolation.bilerp(srgba0[0], y1: ergba0[0], t1: t[0], y2: srgba1[0], y3: ergba1[0], t2: t[1]),
111 | green: Interpolation.bilerp(srgba0[1], y1: ergba0[1], t1: t[0], y2: srgba1[1], y3: ergba1[1], t2: t[1]),
112 | blue: Interpolation.bilerp(srgba0[2], y1: ergba0[2], t1: t[0], y2: srgba1[2], y3: ergba1[2], t2: t[1]),
113 | alpha: Interpolation.bilerp(srgba0[3], y1: ergba0[3], t1: t[0], y2: srgba1[3], y3: ergba1[3], t2: t[1]))
114 |
115 | }
116 | }
117 |
118 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Math/Easing.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright 2015 - Jorge Ouahbi
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 |
18 |
19 | //
20 | // Easing.swift
21 | //
22 | // Created by Jorge Ouahbi on 21/4/16.
23 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
24 | //
25 |
26 | import Foundation
27 |
28 | public typealias EasingFunction = (Double) -> Double
29 | public typealias EasingFunctionsTuple = (function: EasingFunction, name: String)
30 |
31 |
32 | /*
33 |
34 | public var kEasingFunctions : Array = [
35 | (Linear,"Linear"),
36 | (QuadraticEaseIn,"QuadraticEaseIn"),
37 | (QuadraticEaseOut,"QuadraticEaseOut"),
38 | (QuadraticEaseInOut,"QuadraticEaseInOut"),
39 | (CubicEaseIn,"CubicEaseIn"),
40 | (CubicEaseOut,"CubicEaseOut"),
41 | (CubicEaseInOut,"CubicEaseInOut"),
42 | (QuarticEaseIn,"QuarticEaseIn"),
43 | (QuarticEaseOut,"QuarticEaseOut"),
44 | (QuarticEaseInOut,"QuarticEaseInOut"),
45 | (QuinticEaseIn,"QuinticEaseIn"),
46 | (QuinticEaseOut,"QuinticEaseOut"),
47 | (QuinticEaseInOut,"QuinticEaseInOut"),
48 | (SineEaseIn,"SineEaseIn"),
49 | (SineEaseOut,"SineEaseOut"),
50 | (SineEaseInOut,"SineEaseInOut"),
51 | (CircularEaseIn,"CircularEaseIn"),
52 | (CircularEaseOut,"CircularEaseOut"),
53 | (CircularEaseInOut,"CircularEaseInOut"),
54 | (ExponentialEaseIn,"ExponentialEaseIn"),
55 | (ExponentialEaseOut,"ExponentialEaseOut"),
56 | (ExponentialEaseInOut,"ExponentialEaseInOut"),
57 | (ElasticEaseIn,"ElasticEaseIn"),
58 | (ElasticEaseOut,"ElasticEaseOut"),
59 | (ElasticEaseInOut,"ElasticEaseInOut"),
60 | (BackEaseIn,"BackEaseIn"),
61 | (BackEaseOut,"BackEaseOut"),
62 | (BackEaseInOut,"BackEaseInOut"),
63 | (BounceEaseIn,"BounceEaseIn"),
64 | (BounceEaseOut,"BounceEaseOut"),
65 | (BounceEaseInOut,"BounceEaseInOut")
66 | ]
67 |
68 | */
69 | //
70 | // easing.c
71 | //
72 | // Copyright (c) 2011, Auerhaus Development, LLC
73 | //
74 | // This program is free software. It comes without any warranty, to
75 | // the extent permitted by applicable law. You can redistribute it
76 | // and/or modify it under the terms of the Do What The Fuck You Want
77 | // To Public License, Version 2, as published by Sam Hocevar. See
78 | // http://sam.zoy.org/wtfpl/COPYING for more details.
79 | //
80 |
81 | // Modeled after the line y = x
82 | func Linear(_ p: Double) -> Double
83 | {
84 | return p;
85 | }
86 |
87 | // Modeled after the parabola y = x^2
88 | func QuadraticEaseIn(_ p: Double) -> Double
89 | {
90 | return p * p;
91 | }
92 |
93 | // Modeled after the parabola y = -x^2 + 2x
94 | func QuadraticEaseOut(_ p: Double) -> Double
95 | {
96 | return -(p * (p - 2));
97 | }
98 |
99 | // Modeled after the piecewise quadratic
100 | // y = (1/2)((2x)^2) ; [0, 0.5)
101 | // y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
102 | func QuadraticEaseInOut(_ p: Double) -> Double
103 | {
104 | if(p < 0.5)
105 | {
106 | return 2 * p * p;
107 | }
108 | else
109 | {
110 | return (-2 * p * p) + (4 * p) - 1;
111 | }
112 | }
113 |
114 | // Modeled after the cubic y = x^3
115 | func CubicEaseIn(_ p: Double) -> Double
116 | {
117 | return p * p * p;
118 | }
119 |
120 | // Modeled after the cubic y = (x - 1)^3 + 1
121 | func CubicEaseOut(_ p: Double) -> Double
122 | {
123 | let f = (p - 1);
124 | return f * f * f + 1;
125 | }
126 |
127 | // Modeled after the piecewise cubic
128 | // y = (1/2)((2x)^3) ; [0, 0.5)
129 | // y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
130 | func CubicEaseInOut(_ p: Double) -> Double
131 | {
132 | if(p < 0.5)
133 | {
134 | return 4 * p * p * p;
135 | }
136 | else
137 | {
138 | let f = ((2 * p) - 2);
139 | return 0.5 * f * f * f + 1;
140 | }
141 | }
142 |
143 | // Modeled after the quartic x^4
144 | func QuarticEaseIn(_ p: Double) -> Double
145 | {
146 | return p * p * p * p;
147 | }
148 |
149 | // Modeled after the quartic y = 1 - (x - 1)^4
150 | func QuarticEaseOut(_ p: Double) -> Double
151 | {
152 | let f = (p - 1);
153 | return f * f * f * (1 - p) + 1;
154 | }
155 |
156 | // Modeled after the piecewise quartic
157 | // y = (1/2)((2x)^4) ; [0, 0.5)
158 | // y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
159 | func QuarticEaseInOut(_ p: Double) -> Double
160 | {
161 | if(p < 0.5)
162 | {
163 | return 8 * p * p * p * p;
164 | }
165 | else
166 | {
167 | let f = (p - 1);
168 | return -8 * f * f * f * f + 1;
169 | }
170 | }
171 |
172 | // Modeled after the quintic y = x^5
173 | func QuinticEaseIn(_ p: Double) -> Double
174 | {
175 | return p * p * p * p * p;
176 | }
177 |
178 | // Modeled after the quintic y = (x - 1)^5 + 1
179 | func QuinticEaseOut(_ p: Double) -> Double
180 | {
181 | let f = (p - 1);
182 | return f * f * f * f * f + 1;
183 | }
184 |
185 | // Modeled after the piecewise quintic
186 | // y = (1/2)((2x)^5) ; [0, 0.5)
187 | // y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
188 | func QuinticEaseInOut(_ p: Double) -> Double
189 | {
190 | if(p < 0.5)
191 | {
192 | return 16 * p * p * p * p * p;
193 | }
194 | else
195 | {
196 | let f = ((2 * p) - 2);
197 | return 0.5 * f * f * f * f * f + 1;
198 | }
199 | }
200 |
201 | // Modeled after quarter-cycle of sine wave
202 | func SineEaseIn(_ p: Double) -> Double
203 | {
204 | return sin((p - 1) * .pi / 2.0) + 1;
205 | }
206 |
207 | // Modeled after quarter-cycle of sine wave (different phase)
208 | func SineEaseOut(_ p: Double) -> Double
209 | {
210 | return sin(p * .pi / 2.0);
211 | }
212 |
213 | // Modeled after half sine wave
214 | func SineEaseInOut(_ p: Double) -> Double
215 | {
216 | return 0.5 * (1 - cos(p * .pi));
217 | }
218 |
219 | // Modeled after shifted quadrant IV of unit circle
220 | func CircularEaseIn(_ p: Double) -> Double
221 | {
222 | return 1 - sqrt(1 - (p * p));
223 | }
224 |
225 | // Modeled after shifted quadrant II of unit circle
226 | func CircularEaseOut(_ p: Double) -> Double
227 | {
228 | return sqrt((2 - p) * p);
229 | }
230 |
231 | // Modeled after the piecewise circular function
232 | // y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5)
233 | // y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
234 | func CircularEaseInOut(_ p: Double) -> Double
235 | {
236 | if(p < 0.5)
237 | {
238 | return 0.5 * (1 - sqrt(1 - 4 * (p * p)));
239 | }
240 | else
241 | {
242 | return 0.5 * (sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1);
243 | }
244 | }
245 |
246 | // Modeled after the exponential function y = 2^(10(x - 1))
247 | func ExponentialEaseIn(_ p: Double) -> Double
248 | {
249 | return (p == 0.0) ? p : pow(2, 10 * (p - 1));
250 | }
251 |
252 | // Modeled after the exponential function y = -2^(-10x) + 1
253 | func ExponentialEaseOut(_ p: Double) -> Double
254 | {
255 | return (p == 1.0) ? p : 1 - pow(2, -10 * p);
256 | }
257 |
258 | // Modeled after the piecewise exponential
259 | // y = (1/2)2^(10(2x - 1)) ; [0,0.5)
260 | // y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]
261 | func ExponentialEaseInOut(_ p: Double) -> Double
262 | {
263 | if(p == 0.0 || p == 1.0) {return p;}
264 |
265 | if(p < 0.5)
266 | {
267 | return 0.5 * pow(2, (20 * p) - 10);
268 | }
269 | else
270 | {
271 | return -0.5 * pow(2, (-20 * p) + 10) + 1;
272 | }
273 | }
274 |
275 | // Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1))
276 | func ElasticEaseIn(_ p: Double) -> Double
277 | {
278 | return sin(13 * .pi / 2.0 * p) * pow(2, 10 * (p - 1));
279 | }
280 |
281 | // Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1
282 | func ElasticEaseOut(_ p: Double) -> Double
283 | {
284 | return sin(-13 * .pi / 2.0 * (p + 1)) * pow(2, -10 * p) + 1;
285 | }
286 |
287 | // Modeled after the piecewise exponentially-damped sine wave:
288 | // y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
289 | // y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
290 | func ElasticEaseInOut(_ p: Double) -> Double
291 | {
292 | if(p < 0.5)
293 | {
294 | return 0.5 * sin(13 * .pi / 2.0 * (2 * p)) * pow(2, 10 * ((2 * p) - 1));
295 | }
296 | else
297 | {
298 | return 0.5 * (sin(-13 * .pi / 2.0 * ((2 * p - 1) + 1)) * pow(2, -10 * (2 * p - 1)) + 2);
299 | }
300 | }
301 |
302 | // Modeled after the overshooting cubic y = x^3-x*sin(x*pi)
303 | func BackEaseIn(_ p: Double) -> Double
304 | {
305 | return p * p * p - p * sin(p * .pi);
306 | }
307 |
308 | // Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
309 | func BackEaseOut(_ p: Double) -> Double
310 | {
311 | let f = (1 - p);
312 | return 1 - (f * f * f - f * sin(f * .pi));
313 | }
314 |
315 | // Modeled after the piecewise overshooting cubic function:
316 | // y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5)
317 | // y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]
318 | func BackEaseInOut(_ p: Double) -> Double
319 | {
320 | if(p < 0.5)
321 | {
322 | let f = 2 * p;
323 | return 0.5 * (f * f * f - f * sin(f * .pi));
324 | }
325 | else
326 | {
327 | let f = (1 - (2*p - 1));
328 | let fsin = sin(f * .pi)
329 | return 0.5 * (1 - (f * f * f - f * fsin)) + 0.5;
330 | }
331 | }
332 |
333 | func BounceEaseIn(_ p: Double) -> Double
334 | {
335 | return 1 - BounceEaseOut(1 - p);
336 | }
337 |
338 | func BounceEaseOut(_ p: Double) -> Double
339 | {
340 | if(p < 4/11.0)
341 | {
342 | return (121 * p * p)/16.0;
343 | }
344 | else if(p < 8/11.0)
345 | {
346 | return (363/40.0 * p * p) - (99/10.0 * p) + 17/5.0;
347 | }
348 | else if(p < 9/10.0)
349 | {
350 | return (4356/361.0 * p * p) - (35442/1805.0 * p) + 16061/1805.0;
351 | }
352 | else
353 | {
354 | return (54/5.0 * p * p) - (513/25.0 * p) + 268/25.0;
355 | }
356 | }
357 |
358 | func BounceEaseInOut(_ p: Double) -> Double
359 | {
360 | if(p < 0.5)
361 | {
362 | return 0.5 * BounceEaseIn(p*2);
363 | }
364 | else
365 | {
366 | return 0.5 * BounceEaseOut(p * 2 - 1) + 0.5;
367 | }
368 | }
369 |
370 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Math/Interpolation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 |
18 | //
19 | // Interpolation.swift
20 | //
21 | // Created by Jorge Ouahbi on 13/5/16.
22 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
23 | //
24 |
25 | import UIKit
26 |
27 | /// Interpolation type: http://paulbourke.net/miscellaneous/interpolation/
28 | ///
29 | /// - linear: lineal interpolation
30 | /// - exponential: exponential interpolation
31 | /// - cosine: cosine interpolation
32 | /// - cubic: cubic interpolation
33 | /// - bilinear: bilinear interpolation
34 |
35 | enum InterpolationType {
36 | case linear
37 | case exponential
38 | case cosine
39 | case cubic
40 | case bilinear
41 | }
42 |
43 | class Interpolation
44 | {
45 | /// Cubic Interpolation
46 | ///
47 | /// - Parameters:
48 | /// - y0: element 0
49 | /// - y1: element 1
50 | /// - y2: element 2
51 | /// - y3: element 3
52 | /// - t: alpha
53 | /// - Returns: the interpolate value
54 | /// - Note:
55 | /// Paul Breeuwsma proposes the following coefficients for a smoother interpolated curve,
56 | /// which uses the slope between the previous point and the next as the derivative at the current point.
57 | /// This results in what are generally referred to as Catmull-Rom splines.
58 | /// a0 = -0.5*y0 + 1.5*y1 - 1.5*y2 + 0.5*y3;
59 | /// a1 = y0 - 2.5*y1 + 2*y2 - 0.5*y3;
60 | /// a2 = -0.5*y0 + 0.5*y2;
61 | /// a3 = y1;
62 |
63 | class func cubicerp(_ y0:CGFloat,y1:CGFloat,y2:CGFloat,y3:CGFloat,t:CGFloat) -> CGFloat {
64 | var a0:CGFloat
65 | var a1:CGFloat
66 | var a2:CGFloat
67 | var a3:CGFloat
68 | var t2:CGFloat
69 |
70 | assert(t >= 0.0 && t <= 1.0);
71 |
72 | t2 = t*t;
73 | a0 = y3 - y2 - y0 + y1;
74 | a1 = y0 - y1 - a0;
75 | a2 = y2 - y0;
76 | a3 = y1;
77 |
78 | return(a0*t*t2+a1*t2+a2*t+a3);
79 | }
80 |
81 | /// Exponential Interpolation
82 | ///
83 | /// - Parameters:
84 | /// - y0: element 0
85 | /// - y1: element 1
86 | /// - t: alpha
87 | /// - Returns: the interpolate value
88 |
89 | class func eerp(_ y0:CGFloat,y1:CGFloat,t:CGFloat) -> CGFloat {
90 | assert(t >= 0.0 && t <= 1.0);
91 | let end = log(max(Double(y0), 0.01))
92 | let start = log(max(Double(y1), 0.01))
93 | return CGFloat(exp(start - (end + start) * Double(t)))
94 | }
95 |
96 |
97 |
98 | /// Linear Interpolation
99 | ///
100 | /// - Parameters:
101 | /// - y0: element 0
102 | /// - y1: element 1
103 | /// - t: alpha
104 | /// - Returns: the interpolate value
105 | /// - Note:
106 | /// Imprecise method which does not guarantee v = v1 when t = 1, due to floating-point arithmetic error.
107 | /// This form may be used when the hardware has a native Fused Multiply-Add instruction.
108 | /// return v0 + t*(v1-v0);
109 | ///
110 | /// Precise method which guarantees v = v1 when t = 1.
111 | /// (1-t)*v0 + t*v1;
112 |
113 | class func lerp(_ y0:CGFloat,y1:CGFloat,t:CGFloat) -> CGFloat {
114 | assert(t >= 0.0 && t <= 1.0);
115 | let inverse = 1.0 - t;
116 | return inverse * y0 + t * y1
117 | }
118 |
119 | /// Bilinear Interpolation
120 | ///
121 | /// - Parameters:
122 | /// - y0: element 0
123 | /// - y1: element 1
124 | /// - t1: alpha
125 | /// - y2: element 2
126 | /// - y3: element 3
127 | /// - t2: alpha
128 | /// - Returns: the interpolate value
129 |
130 | class func bilerp(_ y0:CGFloat,y1:CGFloat,t1:CGFloat,y2:CGFloat,y3:CGFloat,t2:CGFloat) -> CGFloat {
131 | assert(t1 >= 0.0 && t1 <= 1.0);
132 | assert(t2 >= 0.0 && t2 <= 1.0);
133 |
134 | let x = lerp(y0, y1: y1, t: t1)
135 | let y = lerp(y2, y1: y3, t: t2)
136 |
137 | return lerp(x, y1: y, t: 0.5)
138 | }
139 |
140 | /// Cosine Interpolation
141 | ///
142 | /// - Parameters:
143 | /// - y0: element 0
144 | /// - y1: element 1
145 | /// - t: alpha
146 | /// - Returns: the interpolate value
147 |
148 | class func coserp(_ y0:CGFloat,y1:CGFloat,t:CGFloat) -> CGFloat {
149 | assert(t >= 0.0 && t <= 1.0);
150 | let mu2 = CGFloat(1.0-cos(Double(t) * .pi))/2;
151 | return (y0*(1.0-mu2)+y1*mu2);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/Math/Math.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | //
18 | // Math.swift
19 | //
20 | // Created by Jorge Ouahbi on 9/5/16.
21 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 |
27 | // clamp a number between lower and upper.
28 | public func clamp(_ value: T, lower: T, upper: T) -> T {
29 | return min(max(value, lower), upper)
30 | }
31 |
32 | // is the number between lower and upper.
33 | public func between(_ value: T, lower: T, upper: T , include: Bool = true) -> Bool {
34 | let left = min(lower, upper)
35 | let right = max(lower, upper)
36 | return include ? (value >= left && value <= right) : (value > left && value < right)
37 | }
38 |
39 | // min radius from rectangle
40 | public func minRadius(_ size: CGSize) -> CGFloat {
41 | assert(size != CGSize.zero)
42 | return size.min() * 0.5;
43 | }
44 |
45 | // max radius from a rectangle (pythagoras)
46 | public func maxRadius(_ size: CGSize) -> CGFloat {
47 | assert(size != CGSize.zero)
48 | return 0.5 * sqrt(size.width * size.width + size.height * size.height)
49 | }
50 |
51 | // monotonically increasing function
52 | public func monotonic(_ numberOfElements:Int) -> [CGFloat] {
53 | assert(numberOfElements > 0)
54 | var monotonicFunction:[CGFloat] = []
55 | let numberOfLocations:CGFloat = CGFloat(numberOfElements - 1)
56 | for locationIndex in 0 ..< numberOfElements {
57 | monotonicFunction.append(CGFloat(locationIndex) / numberOfLocations)
58 | }
59 | return monotonicFunction
60 | }
61 |
62 | // redistributes values on a slope (ease-in ease-out)
63 | public func slope( x:Float, A:Float) -> Float {
64 | let p = powf(x,A);
65 | return p/(p + powf(1.0-x, A));
66 | }
67 |
68 | public func linlin( val:Double, inMin:Double, inMax:Double, outMin:Double, outMax:Double) -> Double {
69 | return ((val - inMin) / (inMax - inMin) * (outMax - outMin)) + outMin;
70 | }
71 |
72 | public func linexp( val:Double, inMin:Double, inMax:Double, outMin:Double, outMax:Double) -> Double {
73 | //clipping
74 | let valclamp = max(min(val, inMax), inMin);
75 | return pow((outMax / outMin), (valclamp - inMin) / (inMax - inMin)) * outMin;
76 | }
77 | public func explin(val:Double, inMin:Double, inMax:Double, outMin:Double, outMax:Double) -> Double {
78 | //clipping
79 | let valclamp = max(min(val, inMax), inMin);
80 | return (log(valclamp/inMin) / log(inMax/inMin) * (outMax - outMin)) + outMin;
81 | }
82 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/OMGradientLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OMGradientLayer.swift
3 | //
4 | // Created by Jorge Ouahbi on 19/8/16.
5 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
6 | //
7 |
8 | //
9 | // Copyright 2015 - Jorge Ouahbi
10 | //
11 | // Licensed under the Apache License, Version 2.0 (the "License");
12 | // you may not use this file except in compliance with the License.
13 | // You may obtain a copy of the License at
14 | //
15 | // http://www.apache.org/licenses/LICENSE-2.0
16 | //
17 | // Unless required by applicable law or agreed to in writing, software
18 | // distributed under the License is distributed on an "AS IS" BASIS,
19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | // See the License for the specific language governing permissions and
21 | // limitations under the License.
22 | //
23 |
24 |
25 | import UIKit
26 |
27 | public typealias GradientColors = (UIColor,UIColor)
28 | typealias TransformContextClosure = (_ ctx:CGContext, _ startPoint:CGPoint, _ endPoint:CGPoint, _ startRadius:CGFloat, _ endRadius:CGFloat) -> (Void)
29 |
30 |
31 | open class OMGradientLayer : CALayer, OMGradientLayerProtocol {
32 |
33 | // MARK: - OMColorsAndLocationsProtocol
34 |
35 | @objc open var colors: [UIColor] = [] {
36 | didSet {
37 | // if only exist one color, duplicate it.
38 | if (colors.count == 1) {
39 | let color = colors.first!
40 | colors = [color, color];
41 | }
42 |
43 | // map monochrome colors to rgba colors
44 | colors = colors.map({
45 | return ($0.colorSpace?.model == .monochrome) ?
46 | UIColor(red: $0.components[0],
47 | green : $0.components[0],
48 | blue : $0.components[0],
49 | alpha : $0.components[1]) : $0
50 | })
51 |
52 | self.setNeedsDisplay()
53 | }
54 | }
55 | @objc open var locations : [CGFloat]? = nil {
56 | didSet {
57 | if locations != nil{
58 | locations!.sort { $0 < $1 }
59 | }
60 | self.setNeedsDisplay()
61 | }
62 | }
63 |
64 | open var isAxial : Bool {
65 | return (gradientType == .axial)
66 | }
67 | open var isRadial : Bool {
68 | return (gradientType == .radial)
69 | }
70 |
71 | // MARK: - OMAxialGradientLayerProtocol
72 |
73 | open var gradientType :OMGradientType = .axial {
74 | didSet {
75 | self.setNeedsDisplay();
76 | }
77 | }
78 |
79 | @objc open var startPoint: CGPoint = CGPoint(x: 0.0, y: 0.5) {
80 | didSet {
81 | self.setNeedsDisplay();
82 | }
83 | }
84 | @objc open var endPoint: CGPoint = CGPoint(x: 1.0, y: 0.5) {
85 | didSet{
86 | self.setNeedsDisplay();
87 | }
88 | }
89 |
90 | open var extendsBeforeStart : Bool = false {
91 | didSet {
92 | self.setNeedsDisplay()
93 | }
94 | }
95 | open var extendsPastEnd : Bool = false {
96 | didSet {
97 | self.setNeedsDisplay()
98 | }
99 | }
100 |
101 | // MARK: - OMRadialGradientLayerProtocol
102 | @objc open var startRadius: CGFloat = 0 {
103 | didSet {
104 | startRadius = clamp(startRadius, lower: 0, upper: 1.0)
105 | self.setNeedsDisplay();
106 | }
107 | }
108 | @objc open var endRadius: CGFloat = 0 {
109 | didSet {
110 | endRadius = clamp(endRadius, lower: 0, upper: 1.0)
111 | self.setNeedsDisplay();
112 | }
113 | }
114 |
115 | // MARK: OMMaskeableLayerProtocol
116 | open var lineWidth : CGFloat = 1.0 {
117 | didSet {
118 | self.setNeedsDisplay()
119 | }
120 | }
121 | open var stroke : Bool = false {
122 | didSet {
123 | self.setNeedsDisplay()
124 | }
125 | }
126 | open var path: CGPath? {
127 | didSet {
128 | self.setNeedsDisplay()
129 | }
130 | }
131 |
132 | /// Transform the radial gradient
133 | /// example: oval gradient = CGAffineTransform(scaleX: 2, y: 1.0);
134 |
135 | open var radialTransform: CGAffineTransform = CGAffineTransform.identity {
136 | didSet {
137 | self.setNeedsDisplay()
138 | }
139 | }
140 |
141 |
142 | // Some predefined Gradients (from WebKit)
143 |
144 | public lazy var insetGradient:GradientColors = {
145 | return (UIColor(red:0 / 255.0, green:0 / 255.0,blue: 0 / 255.0,alpha: 0 ),
146 | UIColor(red: 0 / 255.0, green:0 / 255.0,blue: 0 / 255.0,alpha: 0.2 ))
147 |
148 | }()
149 |
150 | public lazy var shineGradient:GradientColors = {
151 | return (UIColor(red:1, green:1,blue: 1,alpha: 0 ),
152 | UIColor(red: 1, green:1,blue:1,alpha: 0.8 ))
153 |
154 | }()
155 |
156 |
157 | public lazy var shadeGradient:GradientColors = {
158 | return (UIColor(red: 252 / 255.0, green: 252 / 255.0,blue: 252 / 255.0,alpha: 0.65 ),
159 | UIColor(red: 178 / 255.0, green:178 / 255.0,blue: 178 / 255.0,alpha: 0.65 ))
160 |
161 | }()
162 |
163 |
164 | public lazy var convexGradient:GradientColors = {
165 | return (UIColor(red:1,green:1,blue:1,alpha: 0.43 ),
166 | UIColor(red:1,green:1,blue:1,alpha: 0.5 ))
167 |
168 | }()
169 |
170 |
171 | public lazy var concaveGradient:GradientColors = {
172 | return (UIColor(red:1,green:1,blue:1,alpha: 0.0 ),
173 | UIColor(red:1,green:1,blue:1,alpha: 0.46 ))
174 |
175 | }()
176 |
177 |
178 | // Here's a method that creates a view that allows 360 degree rotation of its two-colour
179 | // gradient based upon input from a slider (or anything). The incoming slider value
180 | // ("x" variable below) is between 0.0 and 1.0.
181 | //
182 | // At 0.0 the gradient is horizontal (with colour A on top, and colour B below), rotating
183 | // through 360 degrees to value 1.0 (identical to value 0.0 - or a full rotation).
184 | //
185 | // E.g. when x = 0.25, colour A is left and colour B is right. At 0.5, colour A is below
186 | // and colour B is above, 0.75 colour A is right and colour B is left. It rotates anti-clockwise
187 | // from right to left.
188 | //
189 | // It takes four arguments: frame, colourA, colourB and the input value (0-1).
190 | //
191 | // from: http://stackoverflow.com/a/29168654/6387073
192 |
193 |
194 | public class func pointsFromNormalizedAngle(_ normalizedAngle:Double) -> (CGPoint,CGPoint) {
195 |
196 | //x is between 0 and 1, eg. from a slider, representing 0 - 360 degrees
197 | //colour A starts on top, with colour B below
198 | //rotations move anti-clockwise
199 |
200 | //create coordinates
201 | let r = 2.0 * .pi;
202 | let a = pow(sin((r*((normalizedAngle+0.75)/2))),2);
203 | let b = pow(sin((r*((normalizedAngle+0.0)/2))),2);
204 | let c = pow(sin((r*((normalizedAngle+0.25)/2))),2);
205 | let d = pow(sin((r*((normalizedAngle+0.5)/2))),2);
206 |
207 | //set the gradient direction
208 | return (CGPoint(x: a, y: b),CGPoint(x: c, y: d))
209 | }
210 |
211 | // MARK: - Object constructors
212 | required public init?(coder aDecoder: NSCoder) {
213 | super.init(coder:aDecoder)
214 | }
215 |
216 | convenience public init(type:OMGradientType) {
217 | self.init()
218 | self.gradientType = type
219 | }
220 |
221 | // MARK: - Object Overrides
222 | override public init() {
223 | super.init()
224 | self.allowsEdgeAntialiasing = true
225 | self.contentsScale = UIScreen.main.scale
226 | self.needsDisplayOnBoundsChange = true;
227 | self.drawsAsynchronously = true;
228 | }
229 |
230 | override public init(layer: Any) {
231 | super.init(layer: layer)
232 |
233 | if let other = layer as? OMGradientLayer {
234 |
235 | // common
236 | self.colors = other.colors
237 | self.locations = other.locations
238 | self.gradientType = other.gradientType
239 |
240 | // axial gradient properties
241 | self.startPoint = other.startPoint
242 | self.endPoint = other.endPoint
243 | self.extendsBeforeStart = other.extendsBeforeStart
244 | self.extendsPastEnd = other.extendsPastEnd
245 |
246 | // radial gradient properties
247 | self.startRadius = other.startRadius
248 | self.endRadius = other.endRadius
249 |
250 | // OMMaskeableLayerProtocol
251 | self.path = other.path
252 | self.stroke = other.stroke
253 | self.lineWidth = other.lineWidth
254 |
255 | self.radialTransform = other.radialTransform
256 | }
257 | }
258 |
259 | // MARK: - Functions
260 | override open class func needsDisplay(forKey event: String) -> Bool {
261 | if (event == OMGradientLayerProperties.startPoint ||
262 | event == OMGradientLayerProperties.locations ||
263 | event == OMGradientLayerProperties.colors ||
264 | event == OMGradientLayerProperties.endPoint ||
265 | event == OMGradientLayerProperties.startRadius ||
266 | event == OMGradientLayerProperties.endRadius) {
267 | return true
268 | }
269 | return super.needsDisplay(forKey: event)
270 | }
271 |
272 | override open func action(forKey event: String) -> CAAction? {
273 | if (event == OMGradientLayerProperties.startPoint ||
274 | event == OMGradientLayerProperties.locations ||
275 | event == OMGradientLayerProperties.colors ||
276 | event == OMGradientLayerProperties.endPoint ||
277 | event == OMGradientLayerProperties.startRadius ||
278 | event == OMGradientLayerProperties.endRadius) {
279 | return animationActionForKey(event);
280 | }
281 | return super.action(forKey: event)
282 | }
283 |
284 | override open func draw(in ctx: CGContext) {
285 | // super.drawInContext(ctx) do nothing
286 | let clipBoundingBox = ctx.boundingBoxOfClipPath
287 | ctx.clear(clipBoundingBox);
288 | ctx.clip(to: clipBoundingBox)
289 | }
290 |
291 | func prepareContextIfNeeds(_ ctx:CGContext, scale:CGAffineTransform, closure:TransformContextClosure) {
292 |
293 | let sp = self.startPoint * self.bounds.size
294 | let ep = self.endPoint * self.bounds.size
295 | let mr = minRadius(self.bounds.size)
296 | // Scaling transformation and keeping track of the inverse
297 | let invScaleT = scale.inverted();
298 | // Extract the Sx and Sy elements from the inverse matrix (See the Quartz documentation for the math behind the matrices)
299 | let invS = CGPoint(x:invScaleT.a, y:invScaleT.d);
300 | // Transform center and radius of gradient with the inverse
301 | let startPointAffined = CGPoint(x:sp.x * invS.x, y:sp.y * invS.y);
302 | let endPointAffined = CGPoint(x:ep.x * invS.x, y:ep.y * invS.y);
303 | let startRadiusAffined = mr * startRadius * invS.x;
304 | let endRadiusAffined = mr * endRadius * invS.x;
305 | // Draw the gradient with the scale transform on the context
306 | ctx.scaleBy(x: scale.a, y: scale.d);
307 | closure(ctx, startPointAffined, endPointAffined, startRadiusAffined, endRadiusAffined)
308 | // Reset the context
309 | ctx.scaleBy(x: invS.x, y: invS.y);
310 | }
311 |
312 |
313 | func addPathAndClipIfNeeded(_ ctx:CGContext) {
314 | if (self.path != nil) {
315 | ctx.addPath(self.path!);
316 | if (self.stroke) {
317 | ctx.setLineWidth(self.lineWidth);
318 | ctx.replacePathWithStrokedPath();
319 | }
320 | ctx.clip();
321 | }
322 | }
323 |
324 | func isDrawable() -> Bool {
325 | if (colors.count == 0) {
326 | // nothing to do
327 | Log.v("\(self.name ?? "") Unable to do the shading without colors.")
328 | return false
329 | }
330 | if (startPoint.isZero && endPoint.isZero) {
331 | // nothing to do
332 | Log.v("\(self.name ?? "") Start point and end point are {x:0, y:0}.")
333 | return false
334 | }
335 | if (startRadius == endRadius && self.isRadial) {
336 | // nothing to do
337 | Log.v("\(self.name ?? "") Start radius and end radius are equal. \(startRadius) \(endRadius)")
338 | return false
339 | }
340 | return true;
341 | }
342 |
343 |
344 | override open var description:String {
345 | get {
346 | var currentDescription:String = "type: \((self.isAxial ? "Axial" : "Radial")) "
347 | if let locations = locations {
348 | if(locations.count == colors.count) {
349 | _ = zip(colors,locations).compactMap { currentDescription += "color: \($0.0.shortDescription) location: \($0.1) " }
350 | } else {
351 | if (locations.count > 0) {
352 | _ = locations.map({currentDescription += "\($0) "})
353 | }
354 | if (colors.count > 0) {
355 | _ = colors.map({currentDescription += "\($0.shortDescription) "})
356 | }
357 | }
358 | }
359 | if (self.isRadial) {
360 | currentDescription += "center from : \(startPoint) to \(endPoint), radius from : \(startRadius) to \(endRadius)"
361 | } else if (self.isAxial) {
362 | currentDescription += "from : \(startPoint) to \(endPoint)"
363 | }
364 | if (self.extendsPastEnd) {
365 | currentDescription += " draws after end location "
366 | }
367 | if (self.extendsBeforeStart) {
368 | currentDescription += " draws before start location "
369 | }
370 | return currentDescription
371 | }
372 | }
373 | }
374 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/OMGradientLayerProtocols.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // OMGradientLayerProtocol.swift
4 | //
5 | // Created by Jorge Ouahbi on 19/8/16.
6 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
7 | //
8 |
9 | //
10 | // Copyright 2015 - Jorge Ouahbi
11 | //
12 | // Licensed under the Apache License, Version 2.0 (the "License");
13 | // you may not use this file except in compliance with the License.
14 | // You may obtain a copy of the License at
15 | //
16 | // http://www.apache.org/licenses/LICENSE-2.0
17 | //
18 | // Unless required by applicable law or agreed to in writing, software
19 | // distributed under the License is distributed on an "AS IS" BASIS,
20 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 | // See the License for the specific language governing permissions and
22 | // limitations under the License.
23 | //
24 |
25 |
26 | import UIKit
27 |
28 |
29 | public enum OMGradientType : Int {
30 | case axial
31 | case radial
32 | }
33 |
34 | // Animatable Properties
35 | public struct OMGradientLayerProperties {
36 |
37 | // OMGradientLayerProtocol
38 | static var startPoint = "startPoint"
39 | static var startRadius = "startRadius"
40 | static var endPoint = "endPoint"
41 | static var endRadius = "endRadius"
42 | static var colors = "colors"
43 | static var locations = "locations"
44 |
45 | // OMShapeableLayerProtocol
46 | static var lineWidth = "endRadius"
47 | static var stroke = "colors"
48 | static var path = "path"
49 |
50 | };
51 |
52 | public protocol OMShapeableLayerProtocol {
53 | // The path stroke line width.
54 | // Defaults to 1.0. Animatable.
55 | var lineWidth : CGFloat {get set}
56 | // The strokeable flag.
57 | // Defaults to false. Animatable.
58 | var stroke : Bool {get set}
59 | // The path.
60 | // Defaults to nil. Animatable.
61 | var path : CGPath? {get set}
62 | }
63 |
64 | public protocol OMColorsAndLocationsProtocol {
65 | // The array of UIColor objects defining the color of each gradient
66 | // stop. Defaults to nil. Animatable.
67 | var colors: [UIColor] {get set}
68 | // An optional array of CGFloat objects defining the location of each
69 | // gradient stop as a value in the range [0,1]. The values must be
70 | // monotonically increasing. If a nil array is given, the stops are
71 | // assumed to spread uniformly across the [0,1] range. When rendered,
72 | // the colors are mapped to the output colorspace before being
73 | // interpolated. Defaults to nil. Animatable.
74 | var locations : [CGFloat]? {get set}
75 | }
76 |
77 | // Axial Gradient layer Protocol
78 | public protocol OMGradientLayerProtocol : OMShapeableLayerProtocol, OMColorsAndLocationsProtocol {
79 |
80 | //Defaults to CGPoint(x: 0.5,y: 0.0). Animatable.
81 | var startPoint: CGPoint {get set}
82 | //Defaults to CGPoint(x: 0.5,y: 1.0). Animatable.
83 | var endPoint: CGPoint {get set}
84 | var extendsBeforeStart : Bool {get set}
85 | var extendsPastEnd:Bool {get set}
86 | var gradientType : OMGradientType {get set}
87 | // Radial
88 | var startRadius:CGFloat {get set}
89 | var endRadius: CGFloat {get set}
90 | }
91 |
92 |
93 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/OMShadingGradient.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright 2015 - Jorge Ouahbi
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 |
18 |
19 | //
20 | // OMShadingGradient.swift
21 | //
22 | // Created by Jorge Ouahbi on 20/4/16.
23 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
24 | //
25 |
26 |
27 | import Foundation
28 | import UIKit
29 |
30 | // function slope
31 | typealias GradientSlopeFunction = EasingFunction
32 |
33 | // interpolate two UIColors
34 | typealias GradientInterpolationFunction = (UIColor,UIColor,CGFloat) -> UIColor
35 |
36 | public enum GradientFunction {
37 | case linear
38 | case exponential
39 | case cosine
40 | }
41 |
42 | // TODO(jom): add a black and with gradients
43 |
44 | func ShadingFunctionCreate(_ colors : [UIColor],
45 | locations : [CGFloat],
46 | slopeFunction: @escaping GradientSlopeFunction,
47 | interpolationFunction: @escaping GradientInterpolationFunction) -> (UnsafePointer, UnsafeMutablePointer) -> Void
48 | {
49 | return { inData, outData in
50 |
51 | let interp = Double(inData[0])
52 | let alpha = CGFloat(slopeFunction(interp))
53 |
54 | var positionIndex = 0;
55 | let colorCount = colors.count
56 | var stop1Position = locations.first!
57 | var stop1Color = colors[0]
58 |
59 | positionIndex += 1;
60 |
61 | var stop2Position:CGFloat = 0.0
62 | var stop2Color:UIColor;
63 |
64 | if (colorCount > 1) {
65 |
66 | // First stop color
67 | stop2Color = colors[1]
68 |
69 | // When originally are 1 location and 1 color.
70 | // Add the stop2Position to 1.0
71 |
72 | if locations.count == 1 {
73 | stop2Position = 1.0
74 | } else {
75 | // First stop location
76 | stop2Position = locations[1];
77 | }
78 | // Next positon index
79 | positionIndex += 1;
80 |
81 |
82 | } else {
83 | // if we only have one value, that's what we return
84 | stop2Position = stop1Position;
85 | stop2Color = stop1Color;
86 | }
87 |
88 | while (positionIndex < colorCount && stop2Position < alpha) {
89 | stop1Color = stop2Color;
90 | stop1Position = stop2Position;
91 | stop2Color = colors[positionIndex]
92 | stop2Position = locations[positionIndex]
93 | positionIndex += 1;
94 | }
95 |
96 | if (alpha <= stop1Position) {
97 | // if we are less than our lowest position, return our first color
98 | Log.d("(OMShadingGradient) alpha:\(String(format:"%.1f",alpha)) <= position \(String(format:"%.1f",stop1Position)) color \(stop1Color.shortDescription)")
99 | outData[0] = (stop1Color.components[0])
100 | outData[1] = (stop1Color.components[1])
101 | outData[2] = (stop1Color.components[2])
102 | outData[3] = (stop1Color.components[3])
103 |
104 | } else if (alpha >= stop2Position) {
105 | // likewise if we are greater than our highest position, return the last color
106 | Log.d("(OMShadingGradient) alpha:\(String(format:"%.1f",alpha)) >= position \(String(format:"%.1f",stop2Position)) color \(stop1Color.shortDescription)")
107 | outData[0] = (stop2Color.components[0])
108 | outData[1] = (stop2Color.components[1])
109 | outData[2] = (stop2Color.components[2])
110 | outData[3] = (stop2Color.components[3])
111 |
112 | } else {
113 |
114 | // otherwise interpolate between the two
115 | let newPosition = (alpha - stop1Position) / (stop2Position - stop1Position);
116 |
117 | let newColor : UIColor = interpolationFunction(stop1Color, stop2Color, newPosition)
118 |
119 | Log.d("(OMShadingGradient) alpha:\(String(format:"%.1f",alpha)) position \(String(format:"%.1f",newPosition)) color \(newColor.shortDescription)")
120 |
121 | for componentIndex in 0 ..< 3 {
122 | outData[componentIndex] = (newColor.components[componentIndex])
123 | }
124 | // The alpha component is always 1, the shading is always opaque.
125 | outData[3] = 1.0
126 | }
127 | }
128 | }
129 |
130 |
131 | func ShadingCallback(_ infoPointer: UnsafeMutableRawPointer?,
132 | inData: UnsafePointer,
133 | outData: UnsafeMutablePointer) -> Swift.Void {
134 | // Load the UnsafeMutableRawPointer, and call the shadingFunction
135 | var shadingPtr = infoPointer?.load(as: OMShadingGradient.self)
136 | // print(shadingPtr!)
137 | shadingPtr?.shadingFunction(inData, outData)
138 | }
139 |
140 |
141 | public struct OMShadingGradient {
142 | var monotonicLocations: [CGFloat] = []
143 | var locations: [CGFloat]?
144 |
145 | let colors : [UIColor]
146 | let startPoint : CGPoint
147 | let endPoint : CGPoint
148 | let startRadius : CGFloat
149 | let endRadius : CGFloat
150 | let extendsPastStart:Bool
151 | let extendsPastEnd:Bool
152 | let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB()
153 | let slopeFunction: EasingFunction
154 | let functionType : GradientFunction
155 | let gradientType : OMGradientType
156 |
157 | init(colors: [UIColor],
158 | locations: [CGFloat]?,
159 | startPoint: CGPoint,
160 | endPoint: CGPoint,
161 | extendStart: Bool = false,
162 | extendEnd: Bool = false,
163 | functionType: GradientFunction = .linear,
164 | slopeFunction: @escaping EasingFunction = Linear) {
165 |
166 | self.init(colors:colors,
167 | locations: locations,
168 | startPoint: startPoint,
169 | startRadius: 0,
170 | endPoint: endPoint,
171 | endRadius: 0,
172 | extendStart: extendStart,
173 | extendEnd: extendEnd,
174 | gradientType: .axial,
175 | functionType: functionType,
176 | slopeFunction: slopeFunction)
177 | }
178 |
179 | init(colors: [UIColor],
180 | locations: [CGFloat]?,
181 | startPoint: CGPoint,
182 | startRadius: CGFloat,
183 | endPoint: CGPoint,
184 | endRadius: CGFloat,
185 | extendStart: Bool = false,
186 | extendEnd: Bool = false,
187 | functionType: GradientFunction = .linear,
188 | slopeFunction: @escaping EasingFunction = Linear) {
189 |
190 | self.init(colors:colors,
191 | locations: locations,
192 | startPoint: startPoint,
193 | startRadius: startRadius,
194 | endPoint: endPoint,
195 | endRadius: endRadius,
196 | extendStart: extendStart,
197 | extendEnd: extendEnd,
198 | gradientType: .radial,
199 | functionType: functionType,
200 | slopeFunction: slopeFunction)
201 | }
202 |
203 | init(colors: [UIColor],
204 | locations: [CGFloat]?,
205 | startPoint: CGPoint,
206 | startRadius: CGFloat,
207 | endPoint: CGPoint,
208 | endRadius: CGFloat,
209 | extendStart: Bool,
210 | extendEnd: Bool,
211 | gradientType : OMGradientType = .axial,
212 | functionType : GradientFunction = .linear,
213 | slopeFunction: @escaping EasingFunction = Linear)
214 | {
215 | self.locations = locations
216 | self.startPoint = startPoint
217 | self.endPoint = endPoint
218 | self.startRadius = startRadius
219 | self.endRadius = endRadius
220 |
221 | // already checked in OMShadingGradientLayer
222 | assert(colors.count >= 2);
223 |
224 | // if only exist one color, duplicate it.
225 | if (colors.count == 1) {
226 | let color = colors.first!
227 | self.colors = [color,color];
228 | } else {
229 | self.colors = colors
230 | }
231 |
232 | // check the color space of all colors.
233 | if let lastColor = colors.last {
234 | for color in colors {
235 | // must be the same colorspace
236 | assert(lastColor.colorSpace?.model == color.colorSpace?.model,
237 | "unexpected color model \(String(describing: color.colorSpace?.model.name)) != \(String(describing: lastColor.colorSpace?.model.name))")
238 | // and correct model
239 | assert(color.colorSpace?.model == .rgb,"unexpected color space model \(String(describing: color.colorSpace?.model.name))")
240 | if(color.colorSpace?.model != .rgb) {
241 | //TODO: handle different color spaces
242 | Log.w("(OMShadingGradient) : Unsupported color space. model: \(String(describing: color.colorSpace?.model.name))")
243 | }
244 | }
245 | }
246 |
247 | self.slopeFunction = slopeFunction
248 | self.functionType = functionType
249 | self.gradientType = gradientType
250 | self.extendsPastStart = extendStart
251 | self.extendsPastEnd = extendEnd
252 |
253 | // handle nil locations
254 | if let locations = self.locations {
255 | if locations.count > 0 {
256 | monotonicLocations = locations
257 | }
258 | }
259 |
260 | // TODO(jom): handle different number colors and locations
261 |
262 | if (monotonicLocations.count == 0) {
263 | self.monotonicLocations = monotonic(colors.count)
264 | self.locations = self.monotonicLocations
265 | }
266 |
267 | Log.v("(OMShadingGradient): \(monotonicLocations.count) monotonic locations")
268 | Log.v("(OMShadingGradient): \(monotonicLocations)")
269 | }
270 |
271 | lazy var shadingFunction : (UnsafePointer, UnsafeMutablePointer) -> Void = {
272 |
273 | // @default: linear interpolation
274 | var interpolationFunction: GradientInterpolationFunction = UIColor.lerp
275 | switch(self.functionType){
276 | case .linear :
277 | interpolationFunction = UIColor.lerp
278 | break
279 | case .exponential :
280 | interpolationFunction = UIColor.eerp
281 | break
282 | case .cosine :
283 | interpolationFunction = UIColor.coserp
284 | break
285 | }
286 | let colors = self.colors
287 | let locations = self.locations
288 | return ShadingFunctionCreate(colors,
289 | locations: locations!,
290 | slopeFunction: self.slopeFunction,
291 | interpolationFunction: interpolationFunction )
292 | }()
293 |
294 | lazy var handleFunction : CGFunction! = {
295 | var callbacks = CGFunctionCallbacks(version: 0, evaluate: ShadingCallback, releaseInfo: nil)
296 | let infoPointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout.size, alignment: MemoryLayout.alignment)
297 | infoPointer.storeBytes(of: self, as: OMShadingGradient.self)
298 |
299 | return CGFunction(info: infoPointer, // info
300 | domainDimension: 1, // domainDimension
301 | domain: [0, 1], // domain
302 | rangeDimension: 4, // rangeDimension
303 | range: [0, 1, 0, 1, 0, 1, 0, 1], // range
304 | callbacks: &callbacks) // callbacks
305 | }()
306 |
307 | lazy var shadingHandle: CGShading? = {
308 | var callbacks = CGFunctionCallbacks(version: 0, evaluate: ShadingCallback, releaseInfo: nil)
309 | if let handleFunction = self.handleFunction {
310 | if (self.gradientType == .axial) {
311 | return CGShading(axialSpace: self.colorSpace,
312 | start: self.startPoint,
313 | end: self.endPoint,
314 | function: handleFunction,
315 | extendStart: self.extendsPastStart,
316 | extendEnd: self.extendsPastEnd)
317 | } else {
318 | assert(self.gradientType == .radial)
319 | return CGShading(radialSpace: self.colorSpace,
320 | start: self.startPoint,
321 | startRadius: self.startRadius,
322 | end: self.endPoint,
323 | endRadius: self.endRadius,
324 | function: handleFunction,
325 | extendStart: self.extendsPastStart,
326 | extendEnd: self.extendsPastEnd)
327 | }
328 | }
329 |
330 | return nil
331 | }()
332 | }
333 |
--------------------------------------------------------------------------------
/Example/Example/OMShadingGradientLayer/Classes/OMShadingGradientLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 |
18 | import UIKit
19 |
20 | open class OMShadingGradientLayer : OMGradientLayer {
21 |
22 | var shading:[OMShadingGradient] = []
23 | /// Contruct gradient object with a type
24 | convenience public init(type:OMGradientType) {
25 | self.init()
26 | self.gradientType = type;
27 |
28 | if type == .radial {
29 | self.startPoint = CGPoint(x: 0.5,y: 0.5)
30 | self.endPoint = CGPoint(x: 0.5,y: 0.5)
31 | }
32 | }
33 |
34 | // MARK: - Object Overrides
35 | override public init() {
36 | super.init()
37 | }
38 |
39 | /// Slope function
40 | open var slopeFunction: EasingFunction = Linear {
41 | didSet {
42 | setNeedsDisplay();
43 | }
44 | }
45 | /// Interpolation gardient function
46 | open var function: GradientFunction = .linear {
47 | didSet {
48 | setNeedsDisplay();
49 | }
50 | }
51 | /// Contruct gradient object with a layer
52 | override public init(layer: Any) {
53 | super.init(layer: layer as AnyObject)
54 | if let other = layer as? OMShadingGradientLayer {
55 | self.slopeFunction = other.slopeFunction;
56 | }
57 | }
58 | /// Contruct gradient object from NSCoder
59 | required public init?(coder aDecoder: NSCoder) {
60 | super.init(coder: aDecoder)
61 | }
62 |
63 | override open func draw(in ctx: CGContext) {
64 | super.draw(in: ctx)
65 |
66 | var locations :[CGFloat]? = self.locations
67 | var colors :[UIColor] = self.colors
68 | var startPoint : CGPoint = self.startPoint
69 | var endPoint : CGPoint = self.endPoint
70 | var startRadius : CGFloat = self.startRadius
71 | var endRadius : CGFloat = self.endRadius
72 |
73 | let player = presentation()
74 |
75 | if let player = player {
76 |
77 | Log.v("\(self.name ?? "") (presentation) \(player)")
78 |
79 | colors = player.colors
80 | locations = player.locations
81 | startPoint = player.startPoint
82 | endPoint = player.endPoint
83 | startRadius = player.startRadius
84 | endRadius = player.endRadius
85 |
86 | } else {
87 | Log.v("\(self.name ?? "") (model) \(self)")
88 | }
89 |
90 | if isDrawable() {
91 | ctx.saveGState()
92 | // The starting point of the axis, in the shading's target coordinate space.
93 | var start:CGPoint = startPoint * self.bounds.size
94 | // The ending point of the axis, in the shading's target coordinate space.
95 | var end:CGPoint = endPoint * self.bounds.size
96 | // The context must be clipped before scale the matrix.
97 | addPathAndClipIfNeeded(ctx)
98 |
99 | if (self.isAxial) {
100 | if(self.stroke) {
101 | if(self.path != nil) {
102 | // if we are using the stroke, we offset the from and to points
103 | // by half the stroke width away from the center of the stroke.
104 | // Otherwise we tend to end up with fills that only cover half of the
105 | // because users set the start and end points based on the center
106 | // of the stroke.
107 | let hw = self.lineWidth * 0.5;
108 | start = end.projectLine(start,length: hw)
109 | end = start.projectLine(end,length: -hw)
110 | }
111 | }
112 |
113 | ctx.scaleBy(x: self.bounds.size.width,
114 | y: self.bounds.size.height );
115 |
116 | start = start / self.bounds.size
117 | end = end / self.bounds.size
118 | }
119 | else
120 | {
121 | // The starting circle has radius `startRadius' and is centered at
122 | // `start', specified in the shading's target coordinate space. The ending
123 | // circle has radius `endRadius' and is centered at `end', specified in the
124 | // shading's target coordinate space.
125 | }
126 |
127 |
128 |
129 | if !self.radialTransform.isIdentity && !self.isAxial {
130 | // transform the radial context
131 | self.prepareContextIfNeeds(ctx, scale: self.radialTransform,
132 | closure:{(ctx, startPoint, endPoint, startRadius, endRadius) -> (Void) in
133 | var shading = OMShadingGradient(colors: colors,
134 | locations: locations,
135 | startPoint: startPoint ,
136 | startRadius: startRadius,
137 | endPoint:endPoint ,
138 | endRadius: endRadius ,
139 | extendStart: self.extendsBeforeStart,
140 | extendEnd: self.extendsPastEnd,
141 | gradientType: self.gradientType,
142 | functionType: self.function,
143 | slopeFunction: self.slopeFunction)
144 |
145 |
146 | self.shading.append(shading)
147 | if let handle = shading.shadingHandle {
148 | ctx.drawShading(handle)
149 | }
150 |
151 | })
152 | } else {
153 | let minimumRadius = minRadius(self.bounds.size)
154 |
155 | var shading = OMShadingGradient(colors: colors,
156 | locations: locations,
157 | startPoint: start ,
158 | startRadius: startRadius * minimumRadius,
159 | endPoint:end ,
160 | endRadius: endRadius * minimumRadius,
161 | extendStart: self.extendsBeforeStart,
162 | extendEnd: self.extendsPastEnd,
163 | gradientType: self.gradientType,
164 | functionType: self.function,
165 | slopeFunction: self.slopeFunction)
166 | self.shading.append(shading)
167 | if let handle = shading.shadingHandle {
168 | ctx.drawShading(handle)
169 | }
170 | }
171 | ctx.restoreGState();
172 | }
173 | }
174 |
175 | override open var description:String {
176 | get {
177 | var currentDescription:String = super.description
178 | if (self.function == .linear) {
179 | currentDescription += " linear interpolation"
180 | } else if(self.function == .exponential) {
181 | currentDescription += " exponential interpolation"
182 | } else if(self.function == .cosine) {
183 | currentDescription += " cosine interpolation"
184 | }
185 | return currentDescription
186 | }
187 | }
188 | }
189 |
190 |
191 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/OMCircularProgress.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint OMCircularProgress.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'OMCircularProgress'
11 | s.version = '0.4.0'
12 | s.summary = 'Circular progress UIControl with steps, images, text and individual animations.'
13 |
14 | # This description is used to generate tags and improve search results.
15 | # * Think: What does it do? Why did you write it? What is the focus?
16 | # * Try to keep it short, snappy and to the point.
17 | # * Write the description between the DESC delimiters below.
18 | # * Finally, don't worry about the indent, CocoaPods strips it!
19 |
20 | s.description = 'Custom circular progress UIControl with steps, images, text and individual animations.'
21 |
22 | s.homepage = 'https://github.com/jaouahbi/OMCircularProgress'
23 | #s.screenshots = 'https://github.com/jaouahbi/OMCircularProgress/blob/master/ScreenShot/ScreenShot_1.png'
24 | s.license = { :type => 'APACHE 2.0', :file => 'LICENSE' }
25 | s.author = { 'Jorge Ouahbi' => 'jorgeouahbi@gmail.com' }
26 | s.source = { :git => 'https://github.com/jaouahbi/OMCircularProgress.git', :tag => s.version.to_s }
27 | s.social_media_url = 'https://twitter.com/a_c_r_a_t_a'
28 | s.ios.deployment_target = '8.0'
29 | s.source_files = 'OMCircularProgress/Classes/**/*'
30 | end
31 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CALayer+AnimationKeyPath.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 |
18 | //
19 | // CALayer+AnimationKeyPath.swift
20 | //
21 | // Created by Jorge Ouahbi on 26/3/15.
22 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
23 | //
24 | // v1.0
25 |
26 | import UIKit
27 |
28 | public extension CALayer {
29 |
30 | // MARK: - CALayer Animation Helpers
31 |
32 | func animationActionForKey(_ event:String!) -> CABasicAnimation! {
33 | let animation = CABasicAnimation(keyPath: event)
34 | animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
35 | animation.fromValue = self.presentation()!.value(forKey: event);
36 | return animation
37 | }
38 |
39 | func animateKeyPath(_ keyPath : String,
40 | fromValue : AnyObject?,
41 | toValue:AnyObject?,
42 | beginTime:TimeInterval,
43 | duration:TimeInterval,
44 | delegate:AnyObject?)
45 | {
46 | let animation = CABasicAnimation(keyPath:keyPath);
47 |
48 | var currentValue: AnyObject? = self.presentation()?.value(forKey: keyPath) as AnyObject?
49 |
50 | if (currentValue == nil) {
51 | currentValue = fromValue
52 | }
53 |
54 | animation.fromValue = currentValue
55 | animation.toValue = toValue
56 | animation.delegate = delegate as! CAAnimationDelegate?
57 |
58 | if(duration > 0.0){
59 | animation.duration = duration
60 | }
61 | if(beginTime > 0.0){
62 | animation.beginTime = beginTime
63 | }
64 |
65 | animation.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.linear)
66 | animation.setValue(self,forKey:keyPath)
67 | self.add(animation, forKey:keyPath)
68 | self.setValue(toValue,forKey:keyPath)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CALayer+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CALayer+Extensions.swift
3 | //
4 | // Created by Jorge Ouahbi on 24/8/16.
5 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | extension CALayer
11 | {
12 | func animatingRefreshes(_ flag:Bool) {
13 | if(flag) {
14 | self.actions = nil;
15 | } else {
16 | // Disable animating view refreshes
17 | self.actions = [
18 | "position" : NSNull(),
19 | "bounds" : NSNull(),
20 | "contents" : NSNull(),
21 | "shadowColor" : NSNull(),
22 | "shadowOpacity" : NSNull(),
23 | "shadowOffset" : NSNull() ,
24 | "shadowRadius" : NSNull()]
25 | }
26 | }
27 | }
28 |
29 | extension CALayer
30 | {
31 | /// Radians
32 |
33 | func concatTransformRotationZ(_ z:Double = 0.0) {
34 | self.transform = CATransform3DConcat(self.transform, CATransform3DMakeRotation(CGFloat(z), 0.0, 0.0, 1.0))
35 | }
36 | func setTransformRotationZ(_ z:Double = 0.0) {
37 | self.transform = CATransform3DMakeRotation(CGFloat(z), 0.0, 0.0, 1.0)
38 | }
39 |
40 | func getTransformRotationZ() -> Double {
41 | return atan2(Double(transform.m12), Double(transform.m11))
42 | }
43 | }
44 |
45 |
46 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CAShapeLayer+HitTest.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright 2015 - Jorge Ouahbi
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 |
18 |
19 | //
20 | // CAShapeLayer+HitTest.swift
21 | // Created by Jorge Ouahbi on 24/11/15.
22 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
23 | //
24 |
25 | import UIKit
26 | import CoreGraphics
27 |
28 | extension CAShapeLayer {
29 | override open func contains(_ p:CGPoint) -> Bool {
30 | let eoFill:Bool = (convertFromCAShapeLayerFillRule(self.fillRule) == "even-odd")
31 | guard let path = self.path
32 | else { return false }
33 | return path.contains(p, using: eoFill ? .evenOdd : .winding)
34 | }
35 |
36 | func pathBoundingBox() -> CGRect {
37 | guard let path = self.path
38 | else { return CGRect.zero }
39 | return path.boundingBox
40 | }
41 | }
42 |
43 | // Helper function inserted by Swift 4.2 migrator.
44 | fileprivate func convertFromCAShapeLayerFillRule(_ input: CAShapeLayerFillRule) -> String {
45 | return input.rawValue
46 | }
47 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CATransform3D+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | //
18 | // CATransform3DExtension.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/11/15.
21 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 |
27 | public extension CATransform3D {
28 | func affine() -> CGAffineTransform {
29 | return CATransform3DGetAffineTransform(self)
30 | }
31 | }
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CGAffineTransform+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | //
18 | // CGAffineTransformExtension.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/11/15.
21 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 | extension CGAffineTransform : CustomDebugStringConvertible {
27 | public var debugDescription: String {
28 | return NSCoder.string(for: self)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CGAffineTransform+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGAffineTransform+Extensions.swift
3 | // ExampleSwift
4 | //
5 | // Created by Jorge Ouahbi on 8/10/16.
6 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public extension CGAffineTransform {
12 | static func randomRotate() -> CGAffineTransform {
13 | return CGAffineTransform(rotationAngle: CGFloat((drand48() * 360.0).degreesToRadians()))
14 | }
15 | static func randomScale(scaleXMax:CGFloat = 16,scaleYMax:CGFloat = 16) -> CGAffineTransform {
16 | let scaleX = (CGFloat(drand48()) * scaleXMax) + 1.0
17 | let scaleY = (CGFloat(drand48()) * scaleYMax) + 1.0
18 |
19 | let flip:CGFloat = drand48() < 0.5 ? -1 : 1
20 | return CGAffineTransform(scaleX: scaleX, y:scaleY * flip)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CGColorSpace+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 |
18 | //
19 | // CGColorSpace+Extensions.swift
20 | //
21 | // Created by Jorge Ouahbi on 13/5/16.
22 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
23 | //
24 | // v 1.0
25 |
26 | import UIKit
27 |
28 | extension CGColorSpaceModel {
29 | var name : String {
30 | switch self {
31 | case .unknown:return "Unknown"
32 | case .monochrome:return "Monochrome"
33 | case .rgb:return "RGB"
34 | case .cmyk:return "CMYK"
35 | case .lab:return "Lab"
36 | case .deviceN:return "DeviceN"
37 | case .indexed:return "Indexed"
38 | case .pattern:return "Pattern"
39 | case .XYZ:return "XYZ"
40 | @unknown default:
41 | fatalError()
42 | }
43 | }
44 | }
45 |
46 | extension CGColorSpace {
47 | var isUnknown: Bool {
48 | return model == .unknown
49 | }
50 | var isRGB : Bool {
51 | return model == .rgb
52 | }
53 | var isCMYK : Bool {
54 | return model == .cmyk
55 | }
56 | var isLab : Bool {
57 | return model == .lab
58 | }
59 | var isMonochrome : Bool {
60 | return model == .monochrome
61 | }
62 | var isDeviceN : Bool {
63 | return model == .deviceN
64 | }
65 | var isIndexed : Bool {
66 | return model == .indexed
67 | }
68 | var isPattern : Bool {
69 | return model == .pattern
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CGFloat+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGFloat+Extensions.swift
3 | //
4 | // Created by Jorge Ouahbi on 19/8/16.
5 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
6 | //
7 | // v1.0
8 |
9 | import UIKit
10 |
11 |
12 | extension CGFloat {
13 | func format(_ short:Bool)->String {
14 | if(short){
15 | return String(format: "%.1f", self)
16 | }else{
17 | return String(format: "%.6f", self)
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CGFloat+Math.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 |
18 | // CGFloat+Math.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/11/15.
21 | // Copyright d© 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 | /// CGFloat Extension for conversion from/to degrees/radians
27 |
28 |
29 | public extension CGFloat {
30 |
31 | func degreesToRadians () -> CGFloat {
32 | return self * CGFloat(0.01745329252)
33 | }
34 | func radiansToDegrees () -> CGFloat {
35 | return self * CGFloat(57.29577951)
36 | }
37 | mutating func clamp(toLowerValue lowerValue: CGFloat, upperValue: CGFloat){
38 | self = Swift.min(Swift.max(self, lowerValue), upperValue)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CGPoint+Center.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | //
18 | // CGPoint+Center.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/11/15.
21 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 |
27 | extension CGPoint {
28 | public func center(_ size:CGSize) -> CGPoint {
29 | return CGPoint(x:self.x - size.width * 0.5, y:self.y - size.height * 0.5);
30 | }
31 |
32 | public func centerRect(_ size:CGSize) -> CGRect{
33 | return CGRect(origin: self.center(size), size:size)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CGPoint+Extension.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright 2015 - Jorge Ouahbi
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 |
18 | //
19 | // CGPoint+Extension.swift
20 | //
21 | // Created by Jorge Ouahbi on 26/4/16.
22 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
23 | //
24 |
25 | // v1.0
26 |
27 | import UIKit
28 |
29 |
30 | public func ==(lhs: CGPoint, rhs: CGPoint) -> Bool {
31 | return lhs.equalTo(rhs)
32 | }
33 |
34 | public func *(lhs: CGPoint, rhs: CGSize) -> CGPoint {
35 | return CGPoint(x:lhs.x*rhs.width,y: lhs.y*rhs.height)
36 | }
37 |
38 | public func *(lhs: CGPoint, scalar: CGFloat) -> CGPoint {
39 | return CGPoint(x:lhs.x*scalar,y: lhs.y*scalar)
40 | }
41 | public func /(lhs: CGPoint, rhs: CGSize) -> CGPoint {
42 | return CGPoint(x:lhs.x/rhs.width,y: lhs.y/rhs.height)
43 | }
44 |
45 |
46 | extension CGPoint : Hashable {
47 |
48 | public var hashValue: Int {
49 | return self.x.hashValue << MemoryLayout.size ^ self.y.hashValue
50 |
51 | }
52 | var isZero : Bool {
53 | return self.equalTo(CGPoint.zero);
54 | }
55 |
56 | func distance(_ point:CGPoint) -> CGFloat {
57 | let diff = CGPoint(x: self.x - point.x, y: self.y - point.y);
58 | return CGFloat(sqrtf(Float(diff.x*diff.x + diff.y*diff.y)));
59 | }
60 |
61 |
62 | func projectLine( _ point:CGPoint, length:CGFloat) -> CGPoint {
63 |
64 | var newPoint = CGPoint(x: point.x, y: point.y)
65 | let x = (point.x - self.x);
66 | let y = (point.y - self.y);
67 | if (x.floatingPointClass == FloatingPointClassification.negativeZero) {
68 | newPoint.y += length;
69 | } else if (y.floatingPointClass == FloatingPointClassification.negativeZero) {
70 | newPoint.x += length;
71 | } else {
72 | #if CGFLOAT_IS_DOUBLE
73 | let angle = atan(y / x);
74 | newPoint.x += sin(angle) * length;
75 | newPoint.y += cos(angle) * length;
76 | #else
77 | let angle = atanf(Float(y) / Float(x));
78 | newPoint.x += CGFloat(sinf(angle) * Float(length));
79 | newPoint.y += CGFloat(cosf(angle) * Float(length));
80 | #endif
81 | }
82 | return newPoint;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CGRect+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | //
18 | // CGRect+Extension.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/11/15.
21 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 | /// CGRect Extension
27 |
28 | extension CGRect{
29 |
30 | /// Apply affine transform
31 | ///
32 | /// - parameter t: affine transform
33 | mutating func apply(_ t:CGAffineTransform) {
34 | self = self.applying(t)
35 | }
36 | /// Center in rect
37 | ///
38 | /// - parameter mainRect: main rect
39 | ///
40 | /// - returns: center CGRect in main rect
41 | func center(_ mainRect:CGRect) -> CGRect{
42 | let dx = mainRect.midX - self.midX
43 | let dy = mainRect.midY - self.midY
44 | return self.offsetBy(dx: dx, dy: dy);
45 | }
46 | /// Construct with size
47 | ///
48 | /// - parameter size: CGRect size
49 | ///
50 | /// - returns: CGRect
51 | public init(_ size:CGSize) {
52 | self.init(origin: CGPoint.zero,size: size)
53 | }
54 | /// Construct with origin
55 | ///
56 | /// - parameter origin: CGRect origin
57 | ///
58 | /// - returns: CGRect
59 | public init(_ origin:CGPoint) {
60 | self.init(origin: origin,size: CGSize.zero)
61 | }
62 |
63 | /// Min radius from rectangle
64 | public var minRadius:CGFloat {
65 | return size.min() * 0.5;
66 | }
67 |
68 | /// Max radius from a rectangle (pythagoras)
69 | public var maxRadius:CGFloat {
70 | return 0.5 * sqrt(size.width * size.width + size.height * size.height)
71 | }
72 |
73 | /// Construct with points
74 | ///
75 | /// - parameter point1: CGPoint
76 | /// - parameter point2: CGPoint
77 | ///
78 | /// - returns: CGRect
79 | public init(_ point1:CGPoint,point2:CGPoint) {
80 | self.init(point1)
81 | size.width = abs(point2.x-self.origin.x)
82 | size.height = abs(point2.y-self.origin.y)
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/CGSize+Math.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | //
18 | // CGSizeExtension.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/11/15.
21 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 | /**
27 | * @brief CGSize Extension
28 | */
29 | extension CGSize
30 | {
31 | func min() -> CGFloat {
32 | return Swift.min(height,width);
33 | }
34 |
35 | func max() -> CGFloat {
36 | return Swift.max(height,width);
37 | }
38 |
39 | func max(_ other : CGSize) -> CGSize {
40 | return self.max() >= other.max() ? self : other;
41 | }
42 |
43 | func hypot() -> CGFloat {
44 | return CoreGraphics.hypot(height,width)
45 | }
46 |
47 | func center() -> CGPoint {
48 | return CGPoint(x:width * 0.5,y:height * 0.5)
49 | }
50 |
51 | func integral() -> CGSize {
52 | return CGSize(width:round(width),height:round(height))
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/Compatibility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | //
18 | // Compatibility.swift
19 | // ExampleSwift
20 | //
21 | // Created by Jorge Ouahbi on 12/9/16.
22 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
23 | //
24 |
25 | import Foundation
26 |
27 |
28 | #if os(OSX)
29 | import Cocoa
30 | public typealias BezierPath = NSBezierPath
31 | public typealias ViewController = NSViewController
32 | public typealias View = NSView
33 | public typealias Image = NSImage
34 | public typealias Font = NSFont
35 | public typealias GestureRecognizer = NSGestureRecognizer
36 | public typealias TapRecognizer = NSClickGestureRecognizer
37 | public typealias PanRecognizer = NSPanGestureRecognizer
38 | public typealias Button = NSButton
39 | #else
40 | import UIKit
41 | public typealias BezierPath = UIBezierPath
42 | public typealias ViewController = UIViewController
43 | public typealias View = UIView
44 | public typealias Image = UIImage
45 | public typealias Font = UIFont
46 | public typealias GestureRecognizer = UIGestureRecognizer
47 | public typealias TapRecognizer = UITapGestureRecognizer
48 | public typealias PanRecognizer = UIPanGestureRecognizer
49 | public typealias Button = UIButton
50 | #endif
51 |
52 |
53 | #if os(OSX)
54 | // UIKit Compatibility
55 | extension NSBezierPath {
56 | open func addLine(to point: CGPoint) {
57 | self.line(to: point)
58 | }
59 |
60 | open func addCurve(to point: CGPoint, controlPoint1: CGPoint, controlPoint2: CGPoint) {
61 | self.curve(to: point, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
62 | }
63 |
64 | open func addQuadCurve(to point: CGPoint, controlPoint: CGPoint) {
65 | self.curve(to: point, controlPoint1: controlPoint, controlPoint2: controlPoint)
66 | }
67 | }
68 | #endif
69 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/Double+Math.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 |
18 | // Double+Math.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/11/15.
21 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 | /**
27 | * Double Extension for conversion from/to degrees/radians and clamp
28 | */
29 |
30 | public extension Double {
31 |
32 | func degreesToRadians () -> Double {
33 | return self * 0.01745329252
34 | }
35 | func radiansToDegrees () -> Double {
36 | return self * 57.29577951
37 | }
38 |
39 | mutating func clamp(toLowerValue lowerValue: Double, upperValue: Double){
40 | self = min(max(self, lowerValue), upperValue)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/Float+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Float+Extensions.swift
3 | //
4 | // Created by Jorge Ouahbi on 19/8/16.
5 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
6 | //
7 | // v1.0
8 |
9 | import UIKit
10 |
11 |
12 | extension Float {
13 | func format(_ short:Bool)->String {
14 | return CGFloat(self).format(short)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/Float+Math.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 |
18 | // Float+Math.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/11/15.
21 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 | /**
27 | * Float Extension for conversion from/to degrees/radians and clamp
28 | */
29 |
30 | public extension Float {
31 |
32 | func degreesToRadians () -> Float {
33 | return self * 0.01745329252
34 | }
35 | func radiansToDegrees () -> Float {
36 | return self * 57.29577951
37 | }
38 |
39 | mutating func clamp(toLowerValue lowerValue: Float, upperValue: Float){
40 | self = min(max(self, lowerValue), upperValue)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/Interpolation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 |
18 | //
19 | // Interpolation.swift
20 | //
21 | // Created by Jorge Ouahbi on 13/5/16.
22 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
23 | //
24 |
25 | import UIKit
26 |
27 | /// Interpolation type
28 | ///
29 | /// - linear: <#linear description#>
30 | /// - exponential: <#exponential description#>
31 | /// - cosine: <#cosine description#>
32 | /// - cubic: <#cubic description#>
33 | /// - bilinear: <#bilinear description#>
34 |
35 | enum InterpolationType {
36 | case linear
37 | case exponential
38 | case cosine
39 | case cubic
40 | case bilinear
41 | }
42 |
43 | class Interpolation
44 | {
45 | /// Cubic Interpolation
46 | ///
47 | /// - Parameters:
48 | /// - y0: element 0
49 | /// - y1: element 1
50 | /// - y2: element 2
51 | /// - y3: element 3
52 | /// - t: alpha
53 | /// - Returns: the interpolate value
54 | /// - Note:
55 | /// Paul Breeuwsma proposes the following coefficients for a smoother interpolated curve,
56 | /// which uses the slope between the previous point and the next as the derivative at the current point.
57 | /// This results in what are generally referred to as Catmull-Rom splines.
58 | /// a0 = -0.5*y0 + 1.5*y1 - 1.5*y2 + 0.5*y3;
59 | /// a1 = y0 - 2.5*y1 + 2*y2 - 0.5*y3;
60 | /// a2 = -0.5*y0 + 0.5*y2;
61 | /// a3 = y1;
62 |
63 | class func cubicerp(_ y0:CGFloat,y1:CGFloat,y2:CGFloat,y3:CGFloat,t:CGFloat) -> CGFloat {
64 | var a0:CGFloat
65 | var a1:CGFloat
66 | var a2:CGFloat
67 | var a3:CGFloat
68 | var t2:CGFloat
69 |
70 | assert(t >= 0.0 && t <= 1.0);
71 |
72 | t2 = t*t;
73 | a0 = y3 - y2 - y0 + y1;
74 | a1 = y0 - y1 - a0;
75 | a2 = y2 - y0;
76 | a3 = y1;
77 |
78 | return(a0*t*t2+a1*t2+a2*t+a3);
79 | }
80 |
81 | /// Exponential Interpolation
82 | ///
83 | /// - Parameters:
84 | /// - y0: element 0
85 | /// - y1: element 1
86 | /// - t: alpha
87 | /// - Returns: the interpolate value
88 |
89 | class func eerp(_ y0:CGFloat,y1:CGFloat,t:CGFloat) -> CGFloat {
90 | assert(t >= 0.0 && t <= 1.0);
91 | let end = log(max(Double(y0), 0.01))
92 | let start = log(max(Double(y1), 0.01))
93 | return CGFloat(exp(start - (end + start) * Double(t)))
94 | }
95 |
96 |
97 |
98 | /// Linear Interpolation
99 | ///
100 | /// - Parameters:
101 | /// - y0: element 0
102 | /// - y1: element 1
103 | /// - t: alpha
104 | /// - Returns: the interpolate value
105 | /// - Note:
106 | /// Imprecise method which does not guarantee v = v1 when t = 1, due to floating-point arithmetic error.
107 | /// This form may be used when the hardware has a native Fused Multiply-Add instruction.
108 | /// return v0 + t*(v1-v0);
109 | ///
110 | /// Precise method which guarantees v = v1 when t = 1.
111 | /// (1-t)*v0 + t*v1;
112 |
113 | class func lerp(_ y0:CGFloat,y1:CGFloat,t:CGFloat) -> CGFloat {
114 | assert(t >= 0.0 && t <= 1.0);
115 | let inverse = 1.0 - t;
116 | return inverse * y0 + t * y1
117 | }
118 |
119 | /// Bilinear Interpolation
120 | ///
121 | /// - Parameters:
122 | /// - y0: element 0
123 | /// - y1: element 1
124 | /// - t1: alpha
125 | /// - y2: element 2
126 | /// - y3: element 3
127 | /// - t2: alpha
128 | /// - Returns: the interpolate value
129 |
130 | class func bilerp(_ y0:CGFloat,y1:CGFloat,t1:CGFloat,y2:CGFloat,y3:CGFloat,t2:CGFloat) -> CGFloat {
131 | assert(t1 >= 0.0 && t1 <= 1.0);
132 | assert(t2 >= 0.0 && t2 <= 1.0);
133 |
134 | let x = lerp(y0, y1: y1, t: t1)
135 | let y = lerp(y2, y1: y3, t: t2)
136 |
137 | return lerp(x, y1: y, t: 0.5)
138 | }
139 |
140 | /// Cosine Interpolation
141 | ///
142 | /// - Parameters:
143 | /// - y0: element 0
144 | /// - y1: element 1
145 | /// - t: alpha
146 | /// - Returns: the interpolate value
147 |
148 | class func coserp(_ y0:CGFloat,y1:CGFloat,t:CGFloat) -> CGFloat {
149 | assert(t >= 0.0 && t <= 1.0);
150 | let mu2 = CGFloat(1.0-cos(Double(t) * .pi))/2;
151 | return (y0*(1.0-mu2)+y1*mu2);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/NSNumber+Format.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 |
18 | // NSNumber+Format.swift
19 | //
20 | // Created by Jorge Ouaubi on 26/11/15.
21 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import UIKit
25 |
26 | //
27 | // NSNumber extension.
28 | //
29 |
30 | extension NSNumber {
31 | func format(_ formatStyle:CFNumberFormatterStyle,locale:CFLocale = CFLocaleCopyCurrent()) -> String! {
32 | let fmt = CFNumberFormatterCreate( kCFAllocatorDefault , locale,formatStyle)
33 | return CFNumberFormatterCreateStringWithNumber(nil,fmt,self) as String
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/String+Random.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright 2015 - Jorge Ouahbi
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 |
18 |
19 | //
20 | // String+Random.swift
21 | //
22 | // Created by Jorge Ouahbi on 21/11/15.
23 | // Copyright © 2015 Jorge Ouahbi. All rights reserved.
24 | //
25 |
26 | import Foundation
27 |
28 | extension String {
29 | static func random(_ len : Int) -> String? {
30 | let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
31 | let randomString : NSMutableString = NSMutableString(capacity: len)
32 | for _ in 0 ..< len {
33 | let length = UInt32 (letters.length)
34 | let rand = arc4random_uniform(length)
35 | randomString.appendFormat("%C", letters.character(at: Int(rand)))
36 | }
37 | return randomString as String
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/UIBezierPath+Polygon.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BezierPolygon.swift
3 | //
4 | // Created by Jorge Ouahbi on 12/9/16.
5 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
6 | //
7 |
8 | // Based on Erica Sadun code
9 | // https://gist.github.com/erica/c54826fd3411d6db053bfdfe1f64ab54
10 |
11 | import UIKit
12 |
13 | public enum PolygonStyle { case flatsingle, flatdouble, curvesingle, curvedouble, flattruple, curvetruple }
14 |
15 | public struct Bezier {
16 |
17 | static func polygon(
18 | sides sideCount: Int = 5,
19 | radius: CGFloat = 50.0,
20 | startAngle offset: CGFloat = 0.0,
21 | style: PolygonStyle = .curvesingle,
22 | percentInflection: CGFloat = 0.0) -> BezierPath
23 | {
24 | guard sideCount >= 3 else {
25 | Log.e("Bezier polygon construction requires 3+ sides")
26 | return BezierPath()
27 | }
28 |
29 | func pointAt(_ theta: CGFloat, inflected: Bool = false, centered: Bool = false) -> CGPoint {
30 | let inflection = inflected ? percentInflection : 0.0
31 | let r = centered ? 0.0 : radius * (1.0 + inflection)
32 | return CGPoint(
33 | x: r * CGFloat(cos(theta)),
34 | y: r * CGFloat(sin(theta)))
35 | }
36 |
37 | let π = CGFloat(Double.pi); let 𝜏 = 2.0 * π
38 | let path = BezierPath()
39 | let dθ = 𝜏 / CGFloat(sideCount)
40 |
41 | path.move(to: pointAt(0.0 + offset))
42 | switch (percentInflection == 0.0, style) {
43 | case (true, _):
44 | for θ in stride(from: 0.0, through: 𝜏, by: dθ) {
45 | path.addLine(to: pointAt(θ + offset))
46 | }
47 | case (false, .curvesingle):
48 | let cpθ = dθ / 2.0
49 | for θ in stride(from: 0.0, to: 𝜏, by: dθ) {
50 | path.addQuadCurve(
51 | to: pointAt(θ + dθ + offset),
52 | controlPoint: pointAt(θ + cpθ + offset, inflected: true))
53 | }
54 | case (false, .flatsingle):
55 | let cpθ = dθ / 2.0
56 | for θ in stride(from: 0.0, to: 𝜏, by: dθ) {
57 | path.addLine(to: pointAt(θ + cpθ + offset, inflected: true))
58 | path.addLine(to: pointAt(θ + dθ + offset))
59 | }
60 | case (false, .curvedouble):
61 | let (cp1θ, cp2θ) = (dθ / 3.0, 2.0 * dθ / 3.0)
62 | for θ in stride(from: 0.0, to: 𝜏, by: dθ) {
63 | path.addCurve(
64 | to: pointAt(θ + dθ + offset),
65 | controlPoint1: pointAt(θ + cp1θ + offset, inflected: true),
66 | controlPoint2: pointAt(θ + cp2θ + offset, inflected: true)
67 | )
68 | }
69 | case (false, .flatdouble):
70 | let (cp1θ, cp2θ) = (dθ / 3.0, 2.0 * dθ / 3.0)
71 | for θ in stride(from: 0.0, to: 𝜏, by: dθ) {
72 | path.addLine(to: pointAt(θ + cp1θ + offset, inflected: true))
73 | path.addLine(to: pointAt(θ + cp2θ + offset, inflected: true))
74 | path.addLine(to: pointAt(θ + dθ + offset))
75 | }
76 |
77 | case (false, .flattruple):
78 | let (cp1θ, cp2θ) = (dθ / 3.0, 2.0 * dθ / 3.0)
79 | for θ in stride(from: 0.0, to: 𝜏, by: dθ) {
80 | path.addLine(to: pointAt(θ + cp1θ + offset, inflected: true))
81 | path.addLine(to: pointAt(θ + dθ / 2.0 + offset, centered: true))
82 | path.addLine(to: pointAt(θ + cp2θ + offset, inflected: true))
83 | path.addLine(to: pointAt(θ + dθ + offset))
84 | }
85 | case (false, .curvetruple):
86 | let (cp1θ, cp2θ) = (dθ / 3.0, 2.0 * dθ / 3.0)
87 | for θ in stride(from: 0.0, to: 𝜏, by: dθ) {
88 | path.addQuadCurve(
89 | to: pointAt(θ + dθ / 2.0 + offset, centered:true),
90 | controlPoint: pointAt(θ + cp1θ + offset, inflected: true))
91 | path.addQuadCurve(
92 | to: pointAt(θ + dθ + offset),
93 | controlPoint: pointAt(θ + cp2θ + offset, inflected: true))
94 | }
95 | }
96 |
97 | path.close()
98 | return path
99 | }
100 | }
101 |
102 | extension UIBezierPath {
103 |
104 | public class func polygon(sides: Int = 5,
105 | radius: CGFloat = 50.0,
106 | startAngle : CGFloat = 0.0,
107 | style: PolygonStyle = .curvesingle,
108 | percentInflection: CGFloat = 0.0) -> UIBezierPath
109 | {
110 | return Bezier.polygon(
111 | sides:sides,
112 | radius:radius,
113 | startAngle:startAngle,
114 | style: style,
115 | percentInflection:percentInflection)
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/UIBezierPath+Subpaths.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 |
18 | //
19 | // UIBezierPath+Subpaths.swift
20 | //
21 | // Created by Jorge Ouahbi on 22/9/16.
22 | // Copyright © 2016 Jorge Ouahbi . All rights reserved.
23 | //
24 |
25 | import UIKit
26 |
27 | extension CGPath {
28 |
29 |
30 | func forEach( body: @escaping @convention(block) (CGPathElement) -> Void) {
31 | typealias Body = @convention(block) (CGPathElement) -> Void
32 | func callback(info: UnsafeMutableRawPointer?, element: UnsafePointer) {
33 | let body = unsafeBitCast(info, to: Body.self)
34 | body(element.pointee)
35 | }
36 | Log.i("(CGPath) Memory layout \(MemoryLayout.size(ofValue: body))")
37 | let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self)
38 | self.apply(info: unsafeBody, function: callback)
39 | }
40 | }
41 |
42 | extension CGPathElement {
43 | func addToPath(path:UIBezierPath) {
44 | switch (self.type) {
45 | case .closeSubpath:
46 | path.close()
47 | break;
48 | case .moveToPoint:
49 | path.move(to: self.points[0])
50 | break;
51 | case .addLineToPoint:
52 | path.addLine(to: self.points[0])
53 | break;
54 | case .addQuadCurveToPoint:
55 | path.addQuadCurve(to: self.points[0],controlPoint:self.points[1]);
56 | break;
57 | case .addCurveToPoint:
58 | path.addCurve(to: self.points[0],controlPoint1:self.points[1], controlPoint2:self.points[2]);
59 | break;
60 | @unknown default:
61 | fatalError()
62 | }
63 | }
64 | }
65 |
66 | extension UIBezierPath {
67 | func subpaths() -> NSArray? {
68 | let results = NSMutableArray()
69 | var current:UIBezierPath? = nil;
70 |
71 | self.cgPath.forEach { element in
72 | switch (element.type) {
73 | case .moveToPoint:
74 | if let current = current {
75 | results.add(current);
76 | }
77 | current = UIBezierPath()
78 | current!.move(to: element.points[0]);
79 |
80 | case .addQuadCurveToPoint, .addCurveToPoint, .addLineToPoint:
81 | if let current = current {
82 | element.addToPath(path: current);
83 | }
84 |
85 | case .closeSubpath:
86 | current?.close()
87 | if let current = current {
88 | results.add(current);
89 | }
90 | current = nil;
91 | @unknown default:
92 | fatalError()
93 | }
94 | }
95 |
96 | if let current = current {
97 | results.add(current);
98 | }
99 |
100 | return results;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/UIColor+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 | //
17 |
18 | //
19 | // UIColor+Extensions.swift
20 | //
21 | // Created by Jorge Ouahbi on 27/4/16.
22 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
23 | //
24 | // v 1.0 Merged files
25 | // v 1.1 Some clean and better component access
26 |
27 |
28 | import UIKit
29 |
30 | /// Attributes
31 |
32 | let kLuminanceDarkCutoff:CGFloat = 0.6;
33 |
34 | extension UIColor
35 | {
36 | /// chroma RGB
37 | var croma : CGFloat {
38 |
39 | let comp = components
40 | let min1 = min(comp[1], comp[2])
41 | let max1 = max(comp[1], comp[2])
42 |
43 | return max(comp[0], max1) - min(comp[0], min1);
44 |
45 | }
46 | // luma RGB
47 | // WebKit
48 | // luma = (r * 0.2125 + g * 0.7154 + b * 0.0721) * ((double)a / 255.0);
49 |
50 | var luma : CGFloat {
51 | let comp = components
52 | let lumaRed = 0.2126 * Float(comp[0])
53 | let lumaGreen = 0.7152 * Float(comp[1])
54 | let lumaBlue = 0.0722 * Float(comp[2])
55 | let luma = Float(lumaRed + lumaGreen + lumaBlue)
56 |
57 | return CGFloat(luma * Float(components[3]))
58 | }
59 |
60 | var luminance : CGFloat {
61 | let comp = components
62 | let fmin = min(min(comp[0],comp[1]),comp[2])
63 | let fmax = max(max(comp[0],comp[1]),comp[2])
64 | return (fmax + fmin) / 2.0;
65 | }
66 |
67 |
68 | var isLight : Bool {
69 | return self.luma >= kLuminanceDarkCutoff
70 | }
71 |
72 | var isDark : Bool {
73 | return self.luma < kLuminanceDarkCutoff;
74 | }
75 | }
76 |
77 |
78 | extension UIColor {
79 |
80 | public convenience init(hex: String) {
81 | var characterSet = NSCharacterSet.whitespacesAndNewlines
82 | characterSet = characterSet.union(NSCharacterSet(charactersIn: "#") as CharacterSet)
83 | let cString = hex.trimmingCharacters(in: characterSet).uppercased()
84 | if (cString.count != 6) {
85 | self.init(white: 1.0, alpha: 1.0)
86 | } else {
87 | var rgbValue: UInt32 = 0
88 | Scanner(string: cString).scanHexInt32(&rgbValue)
89 |
90 | self.init(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
91 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
92 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
93 | alpha: CGFloat(1.0))
94 | }
95 | }
96 |
97 | // MARK: RGBA components
98 |
99 | /// Returns an array of `CGFloat`s containing four elements with `self`'s:
100 | /// - red (index `0`)
101 | /// - green (index `1`)
102 | /// - blue (index `2`)
103 | /// - alpha (index `3`)
104 | /// or
105 | /// - white (index `0`)
106 | /// - alpha (index `1`)
107 | var components : [CGFloat] {
108 | // Constructs the array in which to store the RGBA-components.
109 | if let components = self.cgColor.components {
110 | if numberOfComponents == 4 {
111 | return [components[0],components[1],components[2],components[3]]
112 | } else {
113 | return [components[0], components[1]]
114 | }
115 | }
116 |
117 | return []
118 | }
119 |
120 | /// red component
121 | var r : CGFloat {
122 | return components[0];
123 | }
124 | /// green component
125 | var g: CGFloat {
126 | return components[1];
127 | }
128 | /// blue component
129 | var b: CGFloat {
130 | return components[2];
131 | }
132 |
133 | /// number of color components
134 | var numberOfComponents : size_t {
135 | return self.cgColor.numberOfComponents
136 | }
137 | /// color space
138 | var colorSpace : CGColorSpace? {
139 | return self.cgColor.colorSpace
140 | }
141 |
142 | // MARK: HSBA components
143 |
144 | /// Returns an array of `CGFloat`s containing four elements with `self`'s:
145 | /// - hue (index `0`)
146 | /// - saturation (index `1`)
147 | /// - brightness (index `2`)
148 | /// - alpha (index `3`)
149 | var hsbaComponents: [CGFloat] {
150 | // Constructs the array in which to store the HSBA-components.
151 |
152 | var components0:CGFloat = 0
153 | var components1:CGFloat = 0
154 | var components2:CGFloat = 0
155 | var components3:CGFloat = 0
156 |
157 | getHue( &components0,
158 | saturation: &components1,
159 | brightness: &components2,
160 | alpha: &components3)
161 |
162 | return [components0,components1,components2,components3];
163 | }
164 | /// alpha component
165 | var alpha : CGFloat {
166 | return self.cgColor.alpha
167 | }
168 | /// hue component
169 | var hue: CGFloat {
170 | return hsbaComponents[0];
171 | }
172 | /// saturation component
173 | var saturation: CGFloat {
174 | return hsbaComponents[1];
175 | }
176 |
177 | /// Returns a lighter color by the provided percentage
178 | ///
179 | /// - param: lighting percent percentage
180 | /// - returns: lighter UIColor
181 |
182 | func lighterColor(percent : Double) -> UIColor {
183 | return colorWithBrightnessFactor(factor: CGFloat(1 + percent));
184 | }
185 |
186 | /// Returns a darker color by the provided percentage
187 | ///
188 | /// - param: darking percent percentage
189 | /// - returns: darker UIColor
190 |
191 | func darkerColor(percent : Double) -> UIColor {
192 | return colorWithBrightnessFactor(factor: CGFloat(1 - percent));
193 | }
194 |
195 | /// Return a modified color using the brightness factor provided
196 | ///
197 | /// - param: factor brightness factor
198 | /// - returns: modified color
199 | func colorWithBrightnessFactor(factor: CGFloat) -> UIColor {
200 | return UIColor(hue: hsbaComponents[0],
201 | saturation: hsbaComponents[1],
202 | brightness: hsbaComponents[2] * factor,
203 | alpha: hsbaComponents[3])
204 |
205 | }
206 |
207 | /// Color difference
208 | ///
209 | /// - Parameter fromColor: color
210 | /// - Returns: Color difference
211 | func difference(fromColor: UIColor) -> Int {
212 | // get the current color's red, green, blue and alpha values
213 | let red:CGFloat = self.components[0]
214 | let green:CGFloat = self.components[1]
215 | let blue:CGFloat = self.components[2]
216 | //var alpha:CGFloat = self.components[3]
217 |
218 | // get the fromColor's red, green, blue and alpha values
219 | let fromRed:CGFloat = fromColor.components[0]
220 | let fromGreen:CGFloat = fromColor.components[1]
221 | let fromBlue:CGFloat = fromColor.components[2]
222 | //var fromAlpha:CGFloat = fromColor.components[3]
223 |
224 | let redValue = (max(red, fromRed) - min(red, fromRed)) * 255
225 | let greenValue = (max(green, fromGreen) - min(green, fromGreen)) * 255
226 | let blueValue = (max(blue, fromBlue) - min(blue, fromBlue)) * 255
227 |
228 | return Int(redValue + greenValue + blueValue)
229 | }
230 |
231 | /// Brightness difference
232 | ///
233 | /// - Parameter fromColor: color
234 | /// - Returns: Brightness difference
235 | func brightnessDifference(fromColor: UIColor) -> Int {
236 | // get the current color's red, green, blue and alpha values
237 | let red:CGFloat = self.components[0]
238 | let green:CGFloat = self.components[1]
239 | let blue:CGFloat = self.components[2]
240 | //var alpha:CGFloat = self.components[3]
241 | let brightness = Int((((red * 299) + (green * 587) + (blue * 114)) * 255) / 1000)
242 |
243 | // get the fromColor's red, green, blue and alpha values
244 | let fromRed:CGFloat = fromColor.components[0]
245 | let fromGreen:CGFloat = fromColor.components[1]
246 | let fromBlue:CGFloat = fromColor.components[2]
247 | //var fromAlpha:CGFloat = fromColor.components[3]
248 |
249 | let fromBrightness = Int((((fromRed * 299) + (fromGreen * 587) + (fromBlue * 114)) * 255) / 1000)
250 |
251 | return max(brightness, fromBrightness) - min(brightness, fromBrightness)
252 | }
253 |
254 | /// Color delta
255 | ///
256 | /// - Parameter color: color
257 | /// - Returns: Color delta
258 | func colorDelta (color: UIColor) -> Double {
259 | var total = CGFloat(0)
260 | total += pow(self.components[0] - color.components[0], 2)
261 | total += pow(self.components[1] - color.components[1], 2)
262 | total += pow(self.components[2] - color.components[2], 2)
263 | total += pow(self.components[3] - color.components[3], 2)
264 | return sqrt(Double(total) * 255.0)
265 | }
266 |
267 | /// Short UIColor description
268 | var shortDescription:String {
269 | let components = self.components
270 | if (numberOfComponents == 2) {
271 | let c = String(format: "%.1f %.1f", components[0], components[1])
272 | if let colorSpace = self.colorSpace {
273 | return "\(colorSpace.model.name):\(c)";
274 | }
275 | return "\(c)";
276 | } else {
277 | assert(numberOfComponents == 4)
278 | let c = String(format: "%.1f %.1f %.1f %.1f", components[0],components[1],components[2],components[3])
279 | if let colorSpace = self.colorSpace {
280 | return "\(colorSpace.model.name):\(c)";
281 | }
282 | return "\(c)";
283 | }
284 | }
285 | }
286 |
287 | /// Random RGBA
288 |
289 | extension UIColor {
290 |
291 | class public func random() -> UIColor? {
292 | let r = CGFloat(drand48())
293 | let g = CGFloat(drand48())
294 | let b = CGFloat(drand48())
295 | return UIColor(red: r, green: g, blue: b, alpha: 1.0)
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/UIColor+Interpolation.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright 2015 - Jorge Ouahbi
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | //
18 |
19 | //
20 | // UIColor+Interpolation.swift
21 | //
22 | // Created by Jorge Ouahbi on 27/4/16.
23 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
24 | //
25 |
26 |
27 | import UIKit
28 |
29 | extension UIColor
30 | {
31 | // RGBA
32 |
33 | /// Linear interpolation
34 | ///
35 | /// - Parameters:
36 | /// - start: start UIColor
37 | /// - end: start UIColor
38 | /// - t: alpha
39 | /// - Returns: return UIColor
40 |
41 | public class func lerp(_ start:UIColor, end:UIColor, t:CGFloat) -> UIColor {
42 |
43 | let srgba = start.components
44 | let ergba = end.components
45 |
46 | return UIColor(red: Interpolation.lerp(srgba[0],y1: ergba[0],t: t),
47 | green: Interpolation.lerp(srgba[1],y1: ergba[1],t: t),
48 | blue: Interpolation.lerp(srgba[2],y1: ergba[2],t: t),
49 | alpha: Interpolation.lerp(srgba[3],y1: ergba[3],t: t))
50 | }
51 |
52 | /// Cosine interpolate
53 | ///
54 | /// - Parameters:
55 | /// - start: start UIColor
56 | /// - end: start UIColor
57 | /// - t: alpha
58 | /// - Returns: return UIColor
59 | public class func coserp(_ start:UIColor, end:UIColor, t:CGFloat) -> UIColor {
60 | let srgba = start.components
61 | let ergba = end.components
62 | return UIColor(red: Interpolation.coserp(srgba[0],y1: ergba[0],t: t),
63 | green: Interpolation.coserp(srgba[1],y1: ergba[1],t: t),
64 | blue: Interpolation.coserp(srgba[2],y1: ergba[2],t: t),
65 | alpha: Interpolation.coserp(srgba[3],y1: ergba[3],t: t))
66 | }
67 |
68 | /// Exponential interpolation
69 | ///
70 | /// - Parameters:
71 | /// - start: start UIColor
72 | /// - end: start UIColor
73 | /// - t: alpha
74 | /// - Returns: return UIColor
75 |
76 | public class func eerp(_ start:UIColor, end:UIColor, t:CGFloat) -> UIColor {
77 | let srgba = start.components
78 | let ergba = end.components
79 |
80 | let r = clamp(Interpolation.eerp(srgba[0],y1: ergba[0],t: t), lower: 0,upper: 1)
81 | let g = clamp(Interpolation.eerp(srgba[1],y1: ergba[1],t: t),lower: 0, upper: 1)
82 | let b = clamp(Interpolation.eerp(srgba[2],y1: ergba[2],t: t), lower: 0, upper: 1)
83 | let a = clamp(Interpolation.eerp(srgba[3],y1: ergba[3],t: t), lower: 0,upper: 1)
84 |
85 | assert(r <= 1.0 && g <= 1.0 && b <= 1.0 && a <= 1.0);
86 |
87 | return UIColor(red: r,
88 | green: g,
89 | blue: b,
90 | alpha: a)
91 |
92 | }
93 |
94 |
95 | /// Bilinear interpolation
96 | ///
97 | /// - Parameters:
98 | /// - start: start UIColor
99 | /// - end: start UIColor
100 | /// - t: alpha
101 | /// - Returns: return UIColor
102 |
103 | public class func bilerp(_ start:[UIColor], end:[UIColor], t:[CGFloat]) -> UIColor {
104 | let srgba0 = start[0].components
105 | let ergba0 = end[0].components
106 |
107 | let srgba1 = start[1].components
108 | let ergba1 = end[1].components
109 |
110 | return UIColor(red: Interpolation.bilerp(srgba0[0], y1: ergba0[0], t1: t[0], y2: srgba1[0], y3: ergba1[0], t2: t[1]),
111 | green: Interpolation.bilerp(srgba0[1], y1: ergba0[1], t1: t[0], y2: srgba1[1], y3: ergba1[1], t2: t[1]),
112 | blue: Interpolation.bilerp(srgba0[2], y1: ergba0[2], t1: t[0], y2: srgba1[2], y3: ergba1[2], t2: t[1]),
113 | alpha: Interpolation.bilerp(srgba0[3], y1: ergba0[3], t1: t[0], y2: srgba1[3], y3: ergba1[3], t2: t[1]))
114 |
115 | }
116 | }
117 |
118 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Extensions/UIFont+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIFont+Extensions.swift
3 | //
4 | // Created by Jorge Ouahbi on 17/11/16.
5 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 |
11 | extension UIFont {
12 | func stringSize(s:String,size:CGSize) -> CGSize {
13 | for i in (4...32).reversed() {
14 | let d = [NSAttributedString.Key.font:UIFont(name:fontName, size:CGFloat(i))!]
15 | let sz = (s as NSString).size(withAttributes: d)
16 | if sz.width <= size.width && sz.height <= size.height {
17 | return sz
18 | }
19 | }
20 | return CGSize.zero
21 | }
22 |
23 | static func stringSize(s:String,fontName:String,size:CGSize) -> CGSize {
24 | for i in (4...32).reversed() {
25 | let d = [NSAttributedString.Key.font:UIFont(name:fontName, size:CGFloat(i))!]
26 | let sz = (s as NSString).size(withAttributes: d)
27 | if sz.width <= size.width && sz.height <= size.height {
28 | return sz
29 | }
30 | }
31 | return CGSize.zero
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/Log.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2017 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 | //
18 | // Log.swift
19 | //
20 | // Created by Jorge Ouahbi on 25/9/16.
21 | // Copyright © 2016 Jorge Ouahbi. All rights reserved.
22 | //
23 |
24 | import Foundation
25 |
26 | // DISABLE_LOG
27 |
28 | // Based on : https://github.com/kostiakoval/SpeedLog
29 |
30 | ///LogLevel type. Specify what level of details should be included to the log
31 | public struct LogLevel : OptionSet {
32 |
33 | public let rawValue: UInt
34 | public init(rawValue: UInt) { self.rawValue = rawValue }
35 |
36 | //MARK:- Options
37 | public static let Debug = LogLevel(rawValue: 1 << 0)
38 | public static let Verbose = LogLevel(rawValue: 1 << 1)
39 | public static let Info = LogLevel(rawValue: 1 << 2)
40 | public static let Warning = LogLevel(rawValue: 1 << 3)
41 | public static let Error = LogLevel(rawValue: 1 << 4)
42 |
43 | /// AllOptions - Enable all options, [Debug, Verbose, Info, Warning, Error]
44 | public static let AllOptions: LogLevel = [Debug, Verbose, Info, Warning, Error]
45 | /// NormalOptions - Enable normal options, [Info, Warning, Error]
46 | public static let NormalOptions: LogLevel = [Warning, Error]
47 | /// DeveloperOptions - Enable normal options, [Info, Warning, Error]
48 | public static let DevOptions: LogLevel = [Verbose, Info, Warning, Error]
49 | /// QAOptions - Enable QA options, [Debug, Verbose, Info, Warning, Error]
50 | public static let DebugOptions: LogLevel = AllOptions
51 |
52 |
53 | }
54 |
55 |
56 | ///Log Type
57 | public struct Log {
58 | /// Log Mode
59 | public static var level: LogLevel = .NormalOptions
60 |
61 | private static func levelName(level:LogLevel) -> String {
62 | switch level {
63 | case LogLevel.Debug:
64 | return "DEBUG"
65 | case LogLevel.Verbose:
66 | return "VERBOSE"
67 | case LogLevel.Info:
68 | return "INFO"
69 | case LogLevel.Warning:
70 | return "WARNING"
71 | case LogLevel.Error:
72 | return "ERROR"
73 | default:
74 | assertionFailure()
75 | return "UNKNOWN"
76 | }
77 | }
78 |
79 | /// print items to the console
80 | ///
81 | /// - parameter items: items to print
82 | /// - parameter level: log level
83 |
84 | public static func print(_ items: Any..., level:LogLevel) {
85 | #if !DISABLE_LOG
86 | let stringItem = items.map {"\($0)"}.joined(separator: " ")
87 | if (Log.level.contains(level)) {
88 | Swift.print("\(levelName(level: level)):\(stringItem)", terminator: "\n")
89 | }
90 | #endif
91 | }
92 | public static func d(_ items: Any..., level:LogLevel = .Debug) {
93 | #if !DISABLE_LOG
94 | print(items,level:level);
95 | #endif
96 | }
97 | public static func w(_ items: Any..., level:LogLevel = .Warning) {
98 | #if !DISABLE_LOG
99 | print(items,level:level);
100 | #endif
101 | }
102 | public static func i(_ items: Any..., level:LogLevel = .Info) {
103 | #if !DISABLE_LOG
104 | print(items,level:level);
105 | #endif
106 | }
107 | public static func e(_ items: Any..., level:LogLevel = .Error) {
108 | #if !DISABLE_LOG
109 | print(items,level:level);
110 | #endif
111 | }
112 | public static func v(_ items: Any..., level:LogLevel = .Verbose) {
113 | #if !DISABLE_LOG
114 | print(items,level:level);
115 | #endif
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/OMLayers/OMNumberLayer/OMNumberLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 |
18 | //
19 | // Created by Jorge Ouahbi on 27/2/15.
20 | //
21 | // Description:
22 | // Simple derived OMTextLayer class that support animation of a number.
23 | //
24 | // Version: 0.0.1 : (7-5-2015)
25 | // Added the NSNumber extension.
26 | //
27 |
28 |
29 | #if os(iOS)
30 | import UIKit
31 | #elseif os(OSX)
32 | import AppKit
33 | #endif
34 |
35 | import CoreText
36 | import CoreFoundation
37 |
38 |
39 | //
40 | // Name of the animatable properties
41 | //
42 |
43 | private struct OMNumberLayerProperties {
44 | static var Number = "number"
45 | }
46 |
47 | //
48 | // CALayer object
49 | //
50 |
51 | class OMNumberLayer : OMTextLayer
52 | {
53 | // MARK: properties
54 |
55 | @objc var number: NSNumber? = nil {
56 | didSet{
57 | setNeedsDisplay()
58 | }
59 | }
60 |
61 | var formatStyle: CFNumberFormatterStyle = CFNumberFormatterStyle.noStyle {
62 | didSet{
63 | setNeedsDisplay()
64 | }
65 | }
66 |
67 | // MARK: constructors
68 |
69 | override init(){
70 | super.init()
71 | }
72 |
73 | convenience init( number : NSNumber ,
74 | formatStyle: CFNumberFormatterStyle = CFNumberFormatterStyle.noStyle,
75 | alignmentMode:String = "center")
76 | {
77 | self.init()
78 | self.number = number
79 | self.formatStyle = formatStyle
80 |
81 | setAlignmentMode(alignmentMode)
82 |
83 | }
84 |
85 | override init(layer: Any) {
86 | super.init(layer: layer as AnyObject)
87 | if let other = layer as? OMNumberLayer {
88 | self.formatStyle = other.formatStyle
89 | self.number = other.number
90 | }
91 | }
92 |
93 | required init?(coder aDecoder: NSCoder) {
94 | super.init(coder:aDecoder)
95 | }
96 |
97 | /**
98 | Calculate the frame size of the NSNumber to represent.
99 |
100 | :note: the max. percent representation is 1
101 |
102 | - parameter number: the number
103 |
104 | - returns: return the frame size needed for represent the string
105 | */
106 | func frameSizeLengthFromNumber(_ number:NSNumber) -> CGSize {
107 | return frameSizeLengthFromAttributedString(NSAttributedString(string : number.format(self.formatStyle)))
108 | }
109 |
110 | func animateNumber(_ fromValue:Double,toValue:Double,beginTime:TimeInterval,duration:TimeInterval,delegate:AnyObject?) {
111 | self.animateKeyPath(OMNumberLayerProperties.Number,
112 | fromValue:fromValue as AnyObject?,
113 | toValue:toValue as AnyObject?,
114 | beginTime:beginTime,
115 | duration:duration,
116 | delegate:delegate)
117 | }
118 |
119 | // MARK: overrides
120 |
121 | override class func needsDisplay(forKey event: String) -> Bool {
122 | if (event == OMNumberLayerProperties.Number) {
123 | return true
124 | }
125 | return super.needsDisplay(forKey: event)
126 | }
127 |
128 | override func action(forKey event: String) -> CAAction? {
129 | if (event == OMNumberLayerProperties.Number) {
130 | return animationActionForKey(event);
131 | }
132 | return super.action(forKey: event)
133 | }
134 |
135 | override func draw(in context: CGContext) {
136 |
137 | var theNumber:NSNumber? = self.number
138 |
139 | if let presentation = self.presentation() {
140 | theNumber = presentation.number
141 | }
142 |
143 | if let num = theNumber {
144 | self.model().string = num.format(self.formatStyle)
145 | }
146 |
147 | // The base class do the work
148 |
149 | super.draw(in: context)
150 |
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/OMCircularProgress/Classes/OMLayers/OMProgressImageLayer/OMProgressImageLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2015 - Jorge Ouahbi
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //
16 |
17 |
18 | //
19 | // OMProgressImageLayer.swift
20 | //
21 | // Created by Jorge Ouahbi on 26/3/15.
22 | //
23 | // 0.1 (29-03-2015)
24 | // Added radial progress, grayscale mode and direction,
25 | // showing/hiding and update shadow copy layer options.
26 | // Now render the image in context
27 | // Update the layer when beginRadians is changed if the type is Circular
28 | // Sets OMProgressType.OMCircular as default type
29 | // Fixed the alpha channel for the grayscaled image
30 | // Added prepareForDrawInContext()
31 |
32 | #if os(iOS)
33 | import UIKit
34 | #elseif os(OSX)
35 | import AppKit
36 | #endif
37 |
38 | public enum OMProgressType : Int
39 | {
40 | case horizontal
41 | case vertical
42 | case circular
43 | case radial
44 | }
45 |
46 | //
47 | // Name of the animatable properties
48 | //
49 |
50 | private struct OMProgressImageLayerProperties {
51 | static var Progress = "progress"
52 | }
53 |
54 | @objc class OMProgressImageLayer : CALayer
55 | {
56 | // progress showing image or hiding
57 |
58 | var showing:Bool = true {
59 | didSet {
60 | setNeedsDisplay()
61 | }
62 | }
63 |
64 | // progress direction
65 |
66 | var clockwise:Bool = true {
67 | didSet {
68 | setNeedsDisplay()
69 | }
70 | }
71 | var image:UIImage? = nil {
72 | didSet {
73 | setNeedsDisplay()
74 | }
75 | }
76 | @objc var progress: Double = 0.0 {
77 | didSet {
78 | setNeedsDisplay()
79 | }
80 | }
81 |
82 | // -90 degrees
83 | var beginRadians: Double = -.pi / 2.0 {
84 | didSet {
85 | if(self.type == .circular) {
86 | setNeedsDisplay()
87 | }
88 | }
89 | }
90 |
91 | var type:OMProgressType = .circular {
92 | didSet {
93 | setNeedsDisplay()
94 | }
95 | }
96 |
97 | var grayScale:Bool = true {
98 | didSet {
99 | setNeedsDisplay()
100 | }
101 | }
102 |
103 | func animateProgress(_ fromValue:Double,toValue:Double,beginTime:TimeInterval,duration:TimeInterval, delegate:AnyObject?) {
104 | self.animateKeyPath(OMProgressImageLayerProperties.Progress,
105 | fromValue:fromValue as AnyObject?,
106 | toValue:toValue as AnyObject?,
107 | beginTime:beginTime,
108 | duration:duration,
109 | delegate:delegate)
110 | }
111 |
112 | override init(){
113 | super.init()
114 | self.contentsScale = UIScreen.main.scale
115 | self.needsDisplayOnBoundsChange = true;
116 |
117 | // https://github.com/danielamitay/iOS-App-Performance-Cheatsheet/blob/master/QuartzCore.md
118 |
119 | self.shouldRasterize = true
120 | self.rasterizationScale = UIScreen.main.scale
121 | self.drawsAsynchronously = true
122 | self.allowsGroupOpacity = true
123 | //self.contentsGravity = "resizeAspect"
124 | }
125 |
126 | convenience init(image:UIImage){
127 | self.init()
128 | self.image = image
129 | }
130 |
131 | override init(layer: Any) {
132 | super.init(layer: layer)
133 | if let other = layer as? OMProgressImageLayer {
134 | self.progress = other.progress
135 | self.image = other.image
136 | self.type = other.type
137 | self.beginRadians = other.beginRadians
138 | self.grayScale = other.grayScale
139 | self.showing = other.showing
140 | self.clockwise = other.clockwise
141 | }
142 | }
143 |
144 | required init?(coder aDecoder: NSCoder) {
145 | super.init(coder:aDecoder)
146 | }
147 |
148 | override class func needsDisplay(forKey event: String) -> Bool {
149 | if(event == OMProgressImageLayerProperties.Progress){
150 | return true
151 | }
152 | return super.needsDisplay(forKey: event)
153 | }
154 |
155 | override func action(forKey event: String) -> CAAction? {
156 | if(event == OMProgressImageLayerProperties.Progress){
157 | return animationActionForKey(event);
158 | }
159 | return super.action(forKey: event)
160 | }
161 |
162 | fileprivate func imageForDrawInContext() -> UIImage? {
163 | var newImage:UIImage? = nil
164 | var newProgress:Double = self.progress
165 |
166 | if let presentationLayer = self.presentation() {
167 | newProgress = presentationLayer.progress
168 | if newProgress == self.progress || (self.progress == 1.0 && newProgress == 0) {
169 | return self.image
170 | } //else{
171 | // print("diferent \(newProgress) != \(self.progress)")
172 | //}
173 | } else {
174 | print("No presentation")
175 | return self.image
176 | }
177 |
178 | if newProgress > 0 {
179 | switch(self.type) {
180 | case .radial:
181 |
182 | let radius = image!.size.max() * CGFloat(newProgress)
183 |
184 | let center = image!.size.center()
185 |
186 | let path = UIBezierPath(arcCenter: center,
187 | radius: radius,
188 | startAngle: 0,
189 | endAngle: CGFloat.pi * 2.0,
190 | clockwise: true)
191 |
192 | path.addLine(to: center)
193 | path.close()
194 |
195 | newImage = self.image!.maskImage(path)
196 | break
197 |
198 |
199 | case .circular:
200 |
201 | let radius = image!.size.max()
202 | let center = image!.size.center()
203 | let startAngle:Double = beginRadians
204 |
205 | let endAngle:Double
206 | if !self.clockwise {
207 | endAngle = Double(Double.pi * 2.0 * (1.0 - newProgress) + beginRadians)
208 | } else {
209 | endAngle = Double(Double.pi * 2.0 * newProgress + beginRadians)
210 | }
211 |
212 | let path = UIBezierPath(arcCenter: center,
213 | radius: radius ,
214 | startAngle: CGFloat(startAngle),
215 | endAngle: CGFloat(endAngle),
216 | clockwise: self.showing)
217 |
218 | path.addLine(to: center)
219 | path.close()
220 | newImage = self.image!.maskImage(path)
221 | break;
222 | case .vertical:
223 | let newHeight = Double(self.bounds.size.height) * newProgress
224 | let path = UIBezierPath(rect: CGRect(CGSize(width:self.bounds.size.width,height:CGFloat(newHeight))))
225 | newImage = self.image!.maskImage(path)
226 | break;
227 | case .horizontal:
228 | let newWidth = Double(self.bounds.size.width) * newProgress
229 | let path = UIBezierPath(rect:CGRect(CGSize(width: CGFloat(newWidth),height:self.bounds.size.height)))
230 | newImage = self.image!.maskImage(path)
231 | break;
232 | }
233 | } else {
234 | if grayScaleWithAlphaImageCached == nil {
235 | grayScaleWithAlphaImageCached = self.image?.grayScaleWithAlphaImage()
236 | }
237 | newImage = grayScaleWithAlphaImageCached
238 | }
239 |
240 | return newImage
241 | }
242 |
243 | var grayScaleWithAlphaImageCached: UIImage? = nil
244 | // MARK: overrides
245 |
246 | override func draw(in context: CGContext) {
247 |
248 | super.draw(in: context)
249 |
250 | // Image setup
251 | let newImage = self.imageForDrawInContext()
252 |
253 | // Core Text Coordinate System and Core Graphics are OSX style
254 | #if os(iOS)
255 | context.translateBy(x: 0, y: self.bounds.size.height);
256 | context.scaleBy(x: 1.0, y: -1.0);
257 | #endif
258 |
259 | if let newImage = newImage {
260 | let rect = CGRect(CGSize(width: newImage.size.width, height: newImage.size.height))
261 | if grayScale {
262 | // original image grayscaled + original image blend
263 | if let image = self.image {
264 | if let grayImage = image.grayScaleWithAlphaImage() {
265 | if let imageBlended = grayImage.blendImage(newImage) {
266 | context.draw(imageBlended.cgImage!, in: rect)
267 | }
268 | }
269 | }
270 | } else {
271 | context.draw(newImage.cgImage!, in: rect)
272 | }
273 | }
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OMCircularProgress
2 |
3 | Custom circular progress UIControl with steps, images, text and individual animations in Swift
4 |
5 | [](http://cocoadocs.org/docsets/OMCircularProgress)
6 | [](https://travis-ci.org/jaouahbi/OMCircularProgress)
7 |
8 |
9 |
10 | ## History
11 |
12 | In 2013, while working for a client specializing in health and fitness.
13 | I had to develop an application that would show progress in stages.
14 | The version I made achieved a very simple and limited representation of the progress, so I decided to write a more complete UIControl from scratch for the representation and visualization of each stage of the task.
15 |
16 | 
17 |
18 | ## Features
19 |
20 | - [x] Steps (Direct/Sequential)
21 | - [x] CoreAnimation based
22 | - [x] Text for each progress step
23 | - [x] Image for each progress step
24 | - [x] Mask layer for each progress step
25 | - [x] Center Text and Image
26 | - [x] CoreText layers
27 | - [x] Progress images
28 |
29 | ## Requirements
30 |
31 | - iOS 10+
32 | - Swift 5
33 |
34 | ## Communication
35 |
36 | Open a issue.
37 |
38 | ## Installation
39 |
40 | > To use OMCircularProgress you must include manually all swift files located inside the `OMCircularProgress` directory directly in your project.
41 |
42 | ## CocoaPods Installation
43 |
44 | > Easiest installation is through Cocoapods. Add the following line to your Podfile:
45 |
46 | pod `OMCircularProgress`
47 | and run pod install in your terminal.
48 |
49 | ## Swift package manager Installation
50 |
51 | > TODO
52 |
53 | * * *
54 |
55 | ## Credits
56 |
57 | OMCircularProgress is owned and maintained by the [Jorge Ouahbi](https://github.com/jaouahbi).
58 |
59 | ## License
60 |
61 | OMCircularProgress is released under the APACHE 2.0 license. See LICENSE for details.
62 |
--------------------------------------------------------------------------------
/ScreenShot/ScreenShot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/ScreenShot/ScreenShot.png
--------------------------------------------------------------------------------
/gif/gif.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaouahbi/OMCircularProgress/96888f9a01c0162cd9d6854d92986df0a9307ec6/gif/gif.gif
--------------------------------------------------------------------------------