├── .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 | [![Version](https://img.shields.io/cocoapods/v/OMCircularProgress.svg?style=flat)](http://cocoadocs.org/docsets/OMCircularProgress) 6 | [![Build Status](https://travis-ci.org/jaouahbi/OMCircularProgress.svg?branch=master)](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 | ![](https://github.com/jaouahbi/OMCircularProgress/blob/master/gif/gif.gif) 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 --------------------------------------------------------------------------------