├── .gitignore ├── LICENSE ├── M13Checkbox Demo Playground ├── LaunchMe.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── bmcquilkin.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── Playground.playground │ ├── Contents.swift │ └── contents.xcplayground ├── M13Checkbox Demo ├── AnimationSelectionTableViewController.swift ├── AppDelegate.swift ├── Assets.xcassets │ ├── Animation Icons │ │ ├── Bounce.imageset │ │ │ ├── Bounce.pdf │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Dot.imageset │ │ │ ├── Contents.json │ │ │ └── Dot.pdf │ │ ├── Expand.imageset │ │ │ ├── Contents.json │ │ │ └── Expand.pdf │ │ ├── Fade.imageset │ │ │ ├── Contents.json │ │ │ └── Fade.pdf │ │ ├── Fill.imageset │ │ │ ├── Contents.json │ │ │ └── Fill.pdf │ │ ├── Flat.imageset │ │ │ ├── Contents.json │ │ │ └── Flat.pdf │ │ ├── Spiral.imageset │ │ │ ├── Contents.json │ │ │ └── Spiral.pdf │ │ └── Stroke.imageset │ │ │ ├── Contents.json │ │ │ └── Stroke.pdf │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-29.png │ │ ├── Icon-29@2x-1.png │ │ ├── Icon-29@2x.png │ │ ├── Icon-29@3x.png │ │ ├── Icon-40.png │ │ ├── Icon-40@2x-1.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ └── Icon-83.5@2x.png │ ├── Contents.json │ ├── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── Icon-83.5.png │ │ ├── Icon-83.5@2x.png │ │ └── Icon-83.5@3x.png │ └── Property Icons │ │ ├── Animation.imageset │ │ ├── Animation.pdf │ │ └── Contents.json │ │ ├── AnimationDuration.imageset │ │ ├── Animation Duration.pdf │ │ └── Contents.json │ │ ├── AnimationStyle.imageset │ │ ├── Animation Style.pdf │ │ └── Contents.json │ │ ├── BoxLineWidth.imageset │ │ ├── Box Line Width.pdf │ │ └── Contents.json │ │ ├── BoxShape.imageset │ │ ├── Box Shape.pdf │ │ └── Contents.json │ │ ├── CheckLineWidth.imageset │ │ ├── Check Line Width.pdf │ │ └── Contents.json │ │ ├── CheckboxState.imageset │ │ ├── Checkbox State.pdf │ │ └── Contents.json │ │ ├── Colors.imageset │ │ ├── Colors.pdf │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── MarkType.imageset │ │ ├── Contents.json │ │ └── Mark Type.pdf │ │ └── Morph.imageset │ │ ├── Contents.json │ │ └── Morph.pdf ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── BaseCollectionViewCell.swift ├── CollectionViewLayout.swift ├── ColorCollectionViewCell.swift ├── DemoViewController.swift ├── Info.plist ├── SegmentedControlCollectionViewCell.swift ├── SelectionCollectionViewCell.swift └── SliderCollectionViewCell.swift ├── M13Checkbox.podspec ├── M13Checkbox.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── M13Checkbox.xcscheme ├── M13Checkbox ├── DefaultValues.swift ├── Info.plist └── M13Checkbox.h ├── Other ├── Math.nb └── Math.pdf ├── Package.swift ├── Podfile ├── Readme.md ├── Resources ├── Animation Duration.pdf ├── Animation Style.pdf ├── Animation.pdf ├── App Icon.sketch ├── Banner.png ├── Banner.sketch ├── Banner@2x.png ├── Bounce.pdf ├── Box Line Width.pdf ├── Box Shape.pdf ├── Check Line Width.pdf ├── Checkbox State.pdf ├── Colors.pdf ├── Dot.pdf ├── Expand.pdf ├── Fade.pdf ├── Fill.pdf ├── Flat.pdf ├── Icon │ ├── Icon-29.png │ ├── Icon-29@2x.png │ ├── Icon-29@3x.png │ ├── Icon-40.png │ ├── Icon-40@2x.png │ ├── Icon-40@3x.png │ ├── Icon-60.png │ ├── Icon-60@2x.png │ ├── Icon-60@3x.png │ ├── Icon-76.png │ ├── Icon-76@2x.png │ ├── Icon-76@3x.png │ ├── Icon-83.5.png │ ├── Icon-83.5@2x.png │ ├── Icon-83.5@3x.png │ ├── iTunesArtwork-512.png │ └── iTunesArtwork-512@2x.png ├── Icons 2.sketch ├── Mark Type.pdf ├── Morph.pdf ├── Samples │ ├── Bounce Fill Sample.gif │ ├── Bounce Stroke Sample.gif │ ├── Dot Fill Sample.gif │ ├── Dot Stroke Sample.gif │ ├── Expand Fill Sample.gif │ ├── Expand Stroke Sample.gif │ ├── Fade Fill Sample.gif │ ├── Fade Stroke Sample.gif │ ├── Fill Sample.gif │ ├── Flat Fill Sample.gif │ ├── Flat Stroke Sample.gif │ ├── Spiral Sample.gif │ └── Stroke Sample.gif ├── Spiral.pdf └── Stroke.pdf └── Sources ├── DefaultValues.swift ├── M13Checkbox+IB.swift ├── M13Checkbox.swift ├── M13CheckboxAnimationGenerator.swift ├── M13CheckboxController.swift ├── M13CheckboxGestureRecognizer.swift ├── Managers ├── M13CheckboxBounceController.swift ├── M13CheckboxDotController.swift ├── M13CheckboxExpandController.swift ├── M13CheckboxFadeController.swift ├── M13CheckboxFillController.swift ├── M13CheckboxFlatController.swift ├── M13CheckboxSpiralController.swift └── M13CheckboxStrokeController.swift └── Paths ├── M13CheckboxAddRemovePathGenerator.swift ├── M13CheckboxCheckPathGenerator.swift ├── M13CheckboxDisclosurePathGenerator.swift ├── M13CheckboxPathGenerator.swift └── M13CheckboxRadioPathGenerator.swift /.gitignore: -------------------------------------------------------------------------------- 1 | M13Checkbox.xcodeproj/*.pbxuser 2 | M13Checkbox.xcodeproj/*.mode* 3 | M13Checkbox.xcodeproj/*.perspectivev3 4 | M13Checkbox.xcodeproj/*.xcworkspace/ 5 | M13Checkbox.xcodeproj/xcuserdata/ 6 | M13Checkbox.xcworkspace/ 7 | Pods/ 8 | Podfile.lock 9 | Playground/LaunchMe.xcworkspace/xcuserdata/* 10 | 11 | # Swift Package Manager 12 | # 13 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 14 | # Packages/ 15 | # Package.pins 16 | # Package.resolved 17 | .build/ 18 | .swiftpm/ 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Brandon McQuilkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /M13Checkbox Demo Playground/LaunchMe.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /M13Checkbox Demo Playground/LaunchMe.xcworkspace/xcuserdata/bmcquilkin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo Playground/LaunchMe.xcworkspace/xcuserdata/bmcquilkin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /M13Checkbox Demo Playground/Playground.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import M13Checkbox 4 | import PlaygroundSupport 5 | 6 | //: Creating A Checkbox 7 | //: ------------------- 8 | 9 | let checkbox = M13Checkbox(frame: CGRect(x: 0.0, y: 0.0, width: 250.0, height: 250.0)) 10 | 11 | //: Managing States 12 | //: --------------- 13 | 14 | // Update the state of the checkbox programatically, with or without animation. 15 | checkbox.setCheckState(.checked, animated: false) 16 | checkbox.setCheckState(.unchecked, animated: false) 17 | 18 | // Or toggle the state 19 | checkbox.toggleCheckState(false) 20 | 21 | // Set values to the checkbox to return depending on its state. 22 | checkbox.checkedValue = 1.0 23 | checkbox.uncheckedValue = 0.0 24 | checkbox.mixedValue = 0.5 25 | 26 | print("Checkbox Value: \(checkbox.value)") 27 | 28 | //: Animations 29 | //: ---------- 30 | 31 | // Update the animation duration 32 | checkbox.animationDuration = 1.0 33 | 34 | // Change the animation used when switching between states. 35 | checkbox.stateChangeAnimation = .bounce(.fill) 36 | 37 | //: Appearance 38 | //: ---------- 39 | 40 | // The background color of the veiw. 41 | checkbox.backgroundColor = .white 42 | // The tint color when in the selected state. 43 | checkbox.tintColor = .yellow 44 | // The tint color when in the unselected state. 45 | checkbox.secondaryTintColor = .green 46 | // The color of the checkmark when the animation is a "fill" style animation. 47 | checkbox.secondaryCheckmarkTintColor = .red 48 | 49 | // Whether or not to display a checkmark, or radio mark. 50 | checkbox.markType = .checkmark 51 | // The line width of the checkmark. 52 | checkbox.checkmarkLineWidth = 2.0 53 | 54 | // The line width of the box. 55 | checkbox.boxLineWidth = 2.0 56 | // The corner radius of the box if it is a square. 57 | checkbox.cornerRadius = 4.0 58 | // Whether the box is a square, or circle. 59 | checkbox.boxType = .circle 60 | // Whether or not to hide the box. 61 | checkbox.hideBox = false 62 | 63 | PlaygroundPage.current.liveView = checkbox 64 | -------------------------------------------------------------------------------- /M13Checkbox Demo Playground/Playground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /M13Checkbox Demo/AnimationSelectionTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationSelectionTableViewController.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 3/11/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import M13Checkbox 11 | 12 | protocol AnimationSelectionTableViewControllerDelegate { 13 | func selectedAnimation(_ animation: M13Checkbox.Animation) 14 | } 15 | 16 | class AnimationSelectionTableViewController: UITableViewController { 17 | 18 | let animations: [M13Checkbox.Animation] = [.stroke, .fill, .bounce(.stroke), .expand(.stroke), .flat(.stroke), .spiral, .fade(.stroke), .dot(.stroke)] 19 | var delegate: AnimationSelectionTableViewControllerDelegate? 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | tableView.backgroundColor = UIColor.clear 25 | tableView.backgroundView?.backgroundColor = UIColor.clear 26 | } 27 | 28 | // MARK: - Table view data source 29 | 30 | override func numberOfSections(in tableView: UITableView) -> Int { 31 | return 1 32 | } 33 | 34 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 35 | // #warning Incomplete implementation, return the number of rows 36 | return animations.count 37 | } 38 | 39 | 40 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 41 | let cell = tableView.dequeueReusableCell(withIdentifier: "animationCell", for: indexPath) 42 | 43 | let animation = animations[(indexPath as NSIndexPath).row] 44 | 45 | switch animation { 46 | case .stroke: 47 | cell.textLabel?.text = "Stroke" 48 | cell.imageView?.image = UIImage(named: "Stroke") 49 | case .fill: 50 | cell.textLabel?.text = "Fill" 51 | cell.imageView?.image = UIImage(named: "Fill") 52 | case .bounce: 53 | cell.textLabel?.text = "Bounce" 54 | cell.imageView?.image = UIImage(named: "Bounce") 55 | case .expand: 56 | cell.textLabel?.text = "Expand" 57 | cell.imageView?.image = UIImage(named: "Expand") 58 | case .flat: 59 | cell.textLabel?.text = "Flat" 60 | cell.imageView?.image = UIImage(named: "Flat") 61 | case .spiral: 62 | cell.textLabel?.text = "Spiral" 63 | cell.imageView?.image = UIImage(named: "Spiral") 64 | case .fade: 65 | cell.textLabel?.text = "Fade" 66 | cell.imageView?.image = UIImage(named: "Fade") 67 | case .dot: 68 | cell.textLabel?.text = "Dot" 69 | cell.imageView?.image = UIImage(named: "Dot") 70 | } 71 | 72 | return cell 73 | } 74 | 75 | override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 76 | cell.backgroundColor = UIColor.clear 77 | cell.contentView.backgroundColor = UIColor.clear 78 | } 79 | 80 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 81 | delegate?.selectedAnimation(animations[(indexPath as NSIndexPath).row]) 82 | presentingViewController?.dismiss(animated: true, completion: nil) 83 | } 84 | 85 | } 86 | 87 | @IBDesignable 88 | class AnimationCell: UITableViewCell { 89 | 90 | @IBInspectable var imageSize: CGSize = CGSize.zero 91 | 92 | 93 | override func layoutSubviews() { 94 | super.layoutSubviews() 95 | if let imageView = imageView { 96 | imageView.frame = CGRect(x: imageView.frame.origin.x, y: (frame.size.height - imageSize.height) / 2.0, width: imageSize.width, height: imageSize.height) 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /M13Checkbox Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 2/23/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let m13CheckboxApplicationColor: UIColor = UIColor(red:0.25, green:0.87, blue:0.92, alpha:1) 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate { 15 | 16 | var window: UIWindow? 17 | 18 | 19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 20 | return true 21 | } 22 | 23 | func applicationWillResignActive(_ application: UIApplication) { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | func applicationDidEnterBackground(_ application: UIApplication) { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | func applicationWillEnterForeground(_ application: UIApplication) { 34 | // 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. 35 | } 36 | 37 | func applicationDidBecomeActive(_ application: UIApplication) { 38 | // 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. 39 | } 40 | 41 | func applicationWillTerminate(_ application: UIApplication) { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Bounce.imageset/Bounce.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Animation Icons/Bounce.imageset/Bounce.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Bounce.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Bounce.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Dot.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Dot.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Dot.imageset/Dot.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Animation Icons/Dot.imageset/Dot.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Expand.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Expand.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Expand.imageset/Expand.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Animation Icons/Expand.imageset/Expand.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Fade.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Fade.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Fade.imageset/Fade.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Animation Icons/Fade.imageset/Fade.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Fill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Fill.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Fill.imageset/Fill.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Animation Icons/Fill.imageset/Fill.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Flat.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Flat.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Flat.imageset/Flat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Animation Icons/Flat.imageset/Flat.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Spiral.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Spiral.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Spiral.imageset/Spiral.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Animation Icons/Spiral.imageset/Spiral.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Stroke.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Stroke.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Animation Icons/Stroke.imageset/Stroke.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Animation Icons/Stroke.imageset/Stroke.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.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 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-29@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-29@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "Icon-60@3x.png", 47 | "scale" : "3x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "20x20", 52 | "scale" : "1x" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "size" : "20x20", 57 | "scale" : "2x" 58 | }, 59 | { 60 | "size" : "29x29", 61 | "idiom" : "ipad", 62 | "filename" : "Icon-29.png", 63 | "scale" : "1x" 64 | }, 65 | { 66 | "size" : "29x29", 67 | "idiom" : "ipad", 68 | "filename" : "Icon-29@2x-1.png", 69 | "scale" : "2x" 70 | }, 71 | { 72 | "size" : "40x40", 73 | "idiom" : "ipad", 74 | "filename" : "Icon-40.png", 75 | "scale" : "1x" 76 | }, 77 | { 78 | "size" : "40x40", 79 | "idiom" : "ipad", 80 | "filename" : "Icon-40@2x-1.png", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "size" : "76x76", 85 | "idiom" : "ipad", 86 | "filename" : "Icon-76.png", 87 | "scale" : "1x" 88 | }, 89 | { 90 | "size" : "76x76", 91 | "idiom" : "ipad", 92 | "filename" : "Icon-76@2x.png", 93 | "scale" : "2x" 94 | }, 95 | { 96 | "size" : "83.5x83.5", 97 | "idiom" : "ipad", 98 | "filename" : "Icon-83.5@2x.png", 99 | "scale" : "2x" 100 | }, 101 | { 102 | "idiom" : "ios-marketing", 103 | "size" : "1024x1024", 104 | "scale" : "1x" 105 | } 106 | ], 107 | "info" : { 108 | "version" : 1, 109 | "author" : "xcode" 110 | } 111 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-29.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-29@2x-1.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-40@2x-1.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Icon-83.5.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Icon-83.5@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Icon-83.5@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/LaunchImage.imageset/Icon-83.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/LaunchImage.imageset/Icon-83.5.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/LaunchImage.imageset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/LaunchImage.imageset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/LaunchImage.imageset/Icon-83.5@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/LaunchImage.imageset/Icon-83.5@3x.png -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/Animation.imageset/Animation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Property Icons/Animation.imageset/Animation.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/Animation.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Animation.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/AnimationDuration.imageset/Animation Duration.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Property Icons/AnimationDuration.imageset/Animation Duration.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/AnimationDuration.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Animation Duration.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/AnimationStyle.imageset/Animation Style.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Property Icons/AnimationStyle.imageset/Animation Style.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/AnimationStyle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Animation Style.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/BoxLineWidth.imageset/Box Line Width.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Property Icons/BoxLineWidth.imageset/Box Line Width.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/BoxLineWidth.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Box Line Width.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/BoxShape.imageset/Box Shape.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Property Icons/BoxShape.imageset/Box Shape.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/BoxShape.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Box Shape.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/CheckLineWidth.imageset/Check Line Width.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Property Icons/CheckLineWidth.imageset/Check Line Width.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/CheckLineWidth.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Check Line Width.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/CheckboxState.imageset/Checkbox State.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Property Icons/CheckboxState.imageset/Checkbox State.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/CheckboxState.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Checkbox State.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/Colors.imageset/Colors.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Property Icons/Colors.imageset/Colors.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/Colors.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Colors.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/MarkType.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Mark Type.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/MarkType.imageset/Mark Type.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Property Icons/MarkType.imageset/Mark Type.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/Morph.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Morph.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /M13Checkbox Demo/Assets.xcassets/Property Icons/Morph.imageset/Morph.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/M13Checkbox Demo/Assets.xcassets/Property Icons/Morph.imageset/Morph.pdf -------------------------------------------------------------------------------- /M13Checkbox Demo/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /M13Checkbox Demo/BaseCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCollectionViewCell.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 3/8/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseCollectionViewCell: UICollectionViewCell { 12 | @IBOutlet weak var iconView: UIImageView? 13 | @IBOutlet weak var titleLabel: UILabel? 14 | @IBOutlet weak var bodyLabel: UILabel? 15 | } 16 | -------------------------------------------------------------------------------- /M13Checkbox Demo/CollectionViewLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewLayout.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 3/9/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CollectionViewLayout: UICollectionViewFlowLayout { 12 | 13 | //---------------------------- 14 | // MARK: - Initalize 15 | //---------------------------- 16 | 17 | override init() { 18 | super.init() 19 | sharedSetup() 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | super.init(coder: aDecoder) 24 | sharedSetup() 25 | } 26 | 27 | fileprivate func sharedSetup() { 28 | scrollDirection = .horizontal 29 | minimumInteritemSpacing = CGFloat.greatestFiniteMagnitude 30 | } 31 | 32 | override func prepare() { 33 | super.prepare() 34 | 35 | guard let collectionView = collectionView else { return } 36 | 37 | // Update the collection view insets. 38 | var insets = UIEdgeInsets.zero 39 | if scrollDirection == .horizontal { 40 | if let leftCellSize = layoutAttributesForItem(at: IndexPath(item: 0, section: 0))?.bounds.size { 41 | insets.left = (collectionView.bounds.size.width - leftCellSize.width) / 2.0 42 | } 43 | 44 | let section = collectionView.numberOfSections - 1 45 | let item = collectionView.numberOfItems(inSection: section) - 1 46 | if let rightCellSize = layoutAttributesForItem(at: IndexPath(item: item, section: section))?.bounds.size { 47 | insets.right = (collectionView.bounds.size.width - rightCellSize.width) / 2.0 48 | } 49 | } else { 50 | if let topCellSize = layoutAttributesForItem(at: IndexPath(item: 0, section: 0))?.bounds.size { 51 | insets.top = (collectionView.bounds.size.height - topCellSize.height) / 2.0 52 | } 53 | 54 | let section = collectionView.numberOfSections - 1 55 | let item = collectionView.numberOfItems(inSection: section) - 1 56 | if let bottomCellSize = layoutAttributesForItem(at: IndexPath(item: item, section: section))?.bounds.size { 57 | insets.bottom = (collectionView.bounds.size.height - bottomCellSize.height) / 2.0 58 | } 59 | } 60 | collectionView.contentInset = insets 61 | 62 | minimumLineSpacing = 100.0 63 | 64 | 65 | 66 | collectionView.decelerationRate = .fast 67 | } 68 | 69 | //---------------------------- 70 | // MARK: - Paging 71 | //---------------------------- 72 | 73 | override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { 74 | 75 | if let collectionView = collectionView { 76 | 77 | let bounds = collectionView.bounds 78 | let center = collectionView.bounds.size.width / 2.0 79 | var proposedContentOffsetCenter: CGFloat = 0.0 80 | if scrollDirection == .horizontal { 81 | proposedContentOffsetCenter = proposedContentOffset.x + center 82 | } else { 83 | proposedContentOffsetCenter = proposedContentOffset.y + center 84 | } 85 | 86 | if let candidateAttributes = layoutAttributesForElements(in: bounds)?.filter({ $0.representedElementCategory == .cell }) { 87 | var candidate: UICollectionViewLayoutAttributes? 88 | 89 | for attributes in candidateAttributes { 90 | if let previousCandidate = candidate { 91 | var a: CGFloat = 0.0 92 | var b: CGFloat = 0.0 93 | if scrollDirection == .horizontal { 94 | a = attributes.center.x - proposedContentOffsetCenter 95 | b = previousCandidate.center.x - proposedContentOffsetCenter 96 | } else { 97 | a = attributes.center.y - proposedContentOffsetCenter 98 | b = previousCandidate.center.y - proposedContentOffsetCenter 99 | } 100 | 101 | if abs(a) < abs(b) { 102 | candidate = attributes 103 | } 104 | } else { 105 | candidate = attributes 106 | } 107 | } 108 | 109 | if scrollDirection == .horizontal { 110 | print("Target X: ", round(candidate!.center.x - center)) 111 | return CGPoint(x: round(candidate!.center.x - center), y: proposedContentOffset.y) 112 | } else { 113 | return CGPoint(x: proposedContentOffset.x, y: round(candidate!.center.x - center)) 114 | } 115 | } 116 | } 117 | 118 | return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity) 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /M13Checkbox Demo/ColorCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorCollectionViewCell.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 3/9/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ColorCollectionViewCell: BaseCollectionViewCell { 12 | 13 | @IBOutlet weak var tintColorButton: UIButton? 14 | @IBOutlet weak var secondaryTintColorButton: UIButton? 15 | @IBOutlet weak var secondaryCheckTintColorButton: UIButton? 16 | @IBOutlet weak var backgroundColorButton: UIButton? 17 | 18 | 19 | override func prepareForReuse() { 20 | tintColorButton?.removeTarget(nil, action: nil, for: .allEvents) 21 | secondaryTintColorButton?.removeTarget(nil, action: nil, for: .allEvents) 22 | secondaryCheckTintColorButton?.removeTarget(nil, action: nil, for: .allEvents) 23 | backgroundColorButton?.removeTarget(nil, action: nil, for: .allEvents) 24 | } 25 | 26 | } 27 | 28 | class ColorButton: UIButton { 29 | 30 | override init(frame: CGRect) { 31 | super.init(frame: frame) 32 | sharedSetup() 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | super.init(coder: aDecoder) 37 | sharedSetup() 38 | } 39 | 40 | fileprivate func sharedSetup() { 41 | layer.borderColor = UIColor.white.cgColor 42 | layer.borderWidth = 1.0 43 | } 44 | 45 | override func layoutSubviews() { 46 | super.layoutSubviews() 47 | layer.cornerRadius = min(bounds.size.width, bounds.size.height) / 2.0 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /M13Checkbox Demo/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 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarTintParameters 34 | 35 | UINavigationBar 36 | 37 | Style 38 | UIBarStyleDefault 39 | Translucent 40 | 41 | 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UISupportedInterfaceOrientations~ipad 50 | 51 | UIInterfaceOrientationPortrait 52 | UIInterfaceOrientationPortraitUpsideDown 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /M13Checkbox Demo/SegmentedControlCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SegmentedControlCollectionViewCell.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 3/8/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SegmentedControlCollectionViewCell: BaseCollectionViewCell { 12 | @IBOutlet weak var segmentedControl: UISegmentedControl? 13 | 14 | override func prepareForReuse() { 15 | segmentedControl?.removeTarget(nil, action: nil, for: .allEvents) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /M13Checkbox Demo/SelectionCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectionCollectionViewCell.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 3/8/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SelectionCollectionViewCell: BaseCollectionViewCell { 12 | @IBOutlet weak var selectionButton: UIButton? 13 | 14 | override func awakeFromNib() { 15 | selectionButton?.layer.cornerRadius = 4.0 16 | selectionButton?.layer.borderColor = selectionButton?.tintColor.cgColor 17 | selectionButton?.layer.borderWidth = 1.0 18 | } 19 | 20 | override func prepareForReuse() { 21 | selectionButton?.removeTarget(nil, action: nil, for: .allEvents) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /M13Checkbox Demo/SliderCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchCollectionViewCell.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 3/8/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SliderCollectionViewCell: BaseCollectionViewCell { 12 | @IBOutlet weak var slider: UISlider? 13 | 14 | override func prepareForReuse() { 15 | slider?.removeTarget(nil, action: nil, for: .allEvents) 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /M13Checkbox.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "M13Checkbox" 3 | s.version = "3.4.0" 4 | s.summary = "A beautiful, customizable, extendable, animated checkbox for iOS." 5 | 6 | s.description = <<-DESC 7 | Create beautiful, customizable, extendable, animated checkboxes on iOS. Completely configurable through interface builder. See the demo app or playground to play with all the features. 8 | DESC 9 | 10 | s.homepage = "https://github.com/Marxon13/M13Checkbox" 11 | s.license = {:type => 'MIT', 12 | :text => <<-LICENSE 13 | Copyright (c) 2016 Brandon McQuilkin 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | 21 | LICENSE 22 | } 23 | 24 | 25 | s.authors = { "Brandon McQuilkin" => "brandon.mcquilkin@gmail.com", "Andrea Antonioni" => "andreaantonioni97@gmail.com" } 26 | 27 | s.platform = :ios, '8.0' 28 | 29 | s.source = { :git => "https://github.com/Marxon13/M13Checkbox.git", :tag => "#{s.version}"} 30 | 31 | s.source_files = 'Sources/**/*' 32 | 33 | s.frameworks = 'Foundation', 'UIKit', 'CoreGraphics' 34 | 35 | s.requires_arc = true 36 | s.swift_version = '5.0' 37 | end 38 | -------------------------------------------------------------------------------- /M13Checkbox.xcodeproj/xcshareddata/xcschemes/M13Checkbox.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /M13Checkbox/DefaultValues.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConstantValues.swift 3 | // M13Checkbox 4 | // 5 | // Created by Andrea Antonioni on 30/07/17. 6 | // Copyright © 2017 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import Foundation 15 | 16 | // A set of default values used to initialize the object 17 | struct DefaultValues { 18 | 19 | static let animation: M13Checkbox.Animation = .stroke 20 | static let markType: M13Checkbox.MarkType = .checkmark 21 | static let boxType: M13Checkbox.BoxType = .circle 22 | static let checkState: M13Checkbox.CheckState = .unchecked 23 | static let controller: M13CheckboxController = M13CheckboxStrokeController() 24 | 25 | } 26 | -------------------------------------------------------------------------------- /M13Checkbox/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.1.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /M13Checkbox/M13Checkbox.h: -------------------------------------------------------------------------------- 1 | // 2 | // M13Checkbox.h 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 4/13/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for M13Checkbox. 12 | FOUNDATION_EXPORT double M13CheckboxVersionNumber; 13 | 14 | //! Project version string for M13Checkbox. 15 | FOUNDATION_EXPORT const unsigned char M13CheckboxVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Other/Math.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Other/Math.pdf -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "M13Checkbox", 8 | platforms: [ 9 | .iOS(.v8), 10 | .macOS(.v10_15) 11 | ], 12 | products: [ 13 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 14 | .library( 15 | name: "M13Checkbox", 16 | targets: ["M13Checkbox"]), 17 | ], 18 | dependencies: [ 19 | // Dependencies declare other packages that this package depends on. 20 | // .package(url: /* package url */, from: "1.0.0"), 21 | ], 22 | targets: [ 23 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 24 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 25 | .target( 26 | name: "M13Checkbox", 27 | dependencies: [], 28 | path: "Sources" 29 | ) 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | use_frameworks! 3 | 4 | target 'M13Checkbox Demo' do 5 | 6 | pod "Color-Picker-for-iOS" 7 | pod "PBDCarouselCollectionViewLayout" 8 | 9 | end 10 | 11 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![Banner](Resources/Banner.png) 4 | 5 |
6 | 7 | Create beautiful, customizable, extendable, animated checkboxes on iOS. Completely configurable through interface builder. It has several built in animations, custom value support, a mixed state, checkmark and radio styles, circular and rounded rectangle box shapes, as well as full color customization. See the demo app to play with all the features. 8 | 9 | ## Table of Contents 10 | 11 | * [**Documentation**](#documentation) 12 | * [Animations](#animations) 13 | * [Values](#values) 14 | * [State](#state) 15 | * [Appearance](#appearance) 16 | * [**Getting Started**](#getting-started) 17 | * [Demo](#demo) 18 | * [Playground](#playground) 19 | * [App](#app) 20 | * [Installation](#installation) 21 | * [Use](#use) 22 | * [**Project Structure**](project-structure) 23 | * [**Project Details**](project-details) 24 | * [Requirements](requirements) 25 | * [Support](support) 26 | * [Todo](todo) 27 | * [License](license) 28 | 29 | ## Documentation 30 | 31 | Check out the demo app to change the properties of the checkbox and see the changes in real time. 32 | 33 | ### Animations 34 | 35 | - **Animation `enum`:** The possible animations for switching to and from the unchecked state. 36 | 37 | - **Stroke:** 38 | 39 | ![Stroke Sample](Resources/Samples/Stroke%20Sample.gif) 40 | - **Fill:** 41 | 42 | ![Fill Sample](Resources/Samples/Fill%20Sample.gif) 43 | - **Bounce (Stroke):** 44 | 45 | ![Bounce Stroke Sample](Resources/Samples/Bounce%20Stroke%20Sample.gif) 46 | - **Bounce (Fill):** 47 | 48 | ![Bounce Fill Sample](Resources/Samples/Bounce%20Fill%20Sample.gif) 49 | - **Expand (Stroke):** 50 | 51 | ![Expand Stroke Sample](Resources/Samples/Expand%20Stroke%20Sample.gif) 52 | - **Expand (Fill):** 53 | 54 | ![Expand Fill Sample](Resources/Samples/Expand%20Fill%20Sample.gif) 55 | - **Flat (Stroke):** 56 | 57 | ![Flat Stroke Sample](Resources/Samples/Flat%20Stroke%20Sample.gif) 58 | - **Flat (Fill):** 59 | 60 | ![Flat Fill Sample](Resources/Samples/Flat%20Fill%20Sample.gif) 61 | - **Spiral:** 62 | 63 | ![Spiral Sample](Resources/Samples/Spiral%20Sample.gif) 64 | - **Fade (Stroke):** 65 | 66 | ![Fade Stroke Sample](Resources/Samples/Fade%20Stroke%20Sample.gif) 67 | - **Fade (Fill):** 68 | 69 | ![Fade Fill Sample](Resources/Samples/Fade%20Fill%20Sample.gif) 70 | - **Dot (Stroke):** 71 | 72 | ![Dot Stroke Sample](Resources/Samples/Dot%20Stroke%20Sample.gif) 73 | - **Dot (Fill):** 74 | 75 | ![Dot Fill Sample](Resources/Samples/Dot%20Fill%20Sample.gif) 76 | 77 | - **stateChangeAnimation `Animation`:** The type of animation to preform when changing from the unchecked state to any other state. 78 | - **animationDuration `NSTimeInterval`:** The duration of the animation that occurs when the checkbox switches states. The default is 0.3 seconds. 79 | 80 | ### Values 81 | 82 | - **value `(Any)`:** Returns either the `checkedValue`, `uncheckedValue`, or `mixedValue` depending on the checkbox's state. 83 | - **checkedValue `Any`:** The object to return from `value` when the checkbox is checked. 84 | - **uncheckedValue `Any`:** The object to return from `value` when the checkbox is unchecked. 85 | - **mixedValue `Any`:** The object to return from `value` when the checkbox is mixed. 86 | 87 | ### State 88 | 89 | - **CheckState `enum`:** The possible states the check can be in. 90 | - `unchecked` — No check is shown. 91 | - `checked` — A checkmark is shown. 92 | - `mixed` — A dash is shown. 93 | - **checkState `CheckState`:** The current state of the checkbox. 94 | - **setCheckState(newState: `CheckState`, animated: `Bool`):** Change the check state with the option of animating the change. 95 | - **toggleCheckState(animated: `Bool` = false):** Toggle the check state between `Unchecked` and `Checked` states. 96 | 97 | ### Appearance 98 | 99 | - **MarkType:** The possible shapes of the mark. 100 | - `checkmark` — The mark is a standard checkmark. 101 | - `radio` — The mark is a radio style fill. 102 | - **BoxType:** The possible shapes of the box. 103 | - `circle` — The box is a circle. 104 | - `square` — The box is square with optional rounded corners. 105 | - **tintColor:** The main color of the `Selected` and `Mixed` states for certain animations. 106 | - **secondaryTintColor `UIColor`:** The color of the box in the unselected state. 107 | - **secondaryCheckmarkTintColor `UIColor`:** The color of the checkmark or radio for certain animations. (Mostly animations with a fill style.) 108 | - **checkmarkLineWidth `CGFloat`:** The line width of the checkmark. 109 | - **markType `MarkType`:** The type of mark to display. 110 | - **boxLineWidth `CGFloat`:** The line width of the box. 111 | - **cornerRadius `CGFloat`:** The corner radius of the box if the box type is `Square`. 112 | - **boxType `BoxType`:** The shape of the checkbox. 113 | - **hideBox `Bool`:** Wether or not to hide the box. 114 | 115 |
116 | 117 | ## Getting Started 118 | 119 | ### Demo 120 | 121 | #### Playground 122 | 123 | To see a working playground in action, run the workspace located at path `M13Checkbox Demo Playground/LaunchMe.xcworkspace`. You may need to run the framework scheme and wait for Xcode to process the files, before the playground begins. Open the assistant editor for a live preview of the UI. 124 | 125 | This is a great way to work on customizing the checkbox in code to suit your needs. 126 | 127 | #### App 128 | 129 | To see the checkbox working on a device, run the demo app included in `M13Checkbox.xcodeproj`. The demo app walks through all the available features. You will need to run a `pod install` in order to build the demo app. 130 | 131 | ### Installation 132 | 133 | #### Cocoapods 134 | 135 | The easiest way to install M13Checkbox is through CocoaPods. Simplify add the following to your podfile. 136 | 137 | ``` 138 | pod 'M13Checkbox' 139 | ``` 140 | 141 | #### Carthage 142 | 143 | To install via Carthage, add the following to your cartfile: 144 | 145 | ``` 146 | github "Marxon13/M13Checkbox" 147 | ``` 148 | 149 | #### Swift Package Manager 150 | 151 | M13Checkbox supports SPM versions 5.1.0 and above. To use SPM, you should use Xcode 11 to open your project. Click `File` -> `Swift Packages` -> `Add Package Dependency`, enter `https://github.com/Marxon13/M13Checkbox`. Select the version you’d like to use. 152 | 153 | You can also manually add the package to your Package.swift file: 154 | 155 | ```swift 156 | .package(url: "https://github.com/Marxon13/M13Checkbox.git", from: "3.4.0") 157 | ``` 158 | Note: IBDesignables and IBInspectables will not work in interface builder. 159 | 160 | Workaround: Create IBDesignable subclass of M13Checkbox, Use this subclass as custom class in interface builder. Example: 161 | ```swift 162 | @IBDesignable 163 | class M13CheckboxView : M13Checkbox {} 164 | ``` 165 | 166 | #### Manual 167 | 168 | Another option is to copy the files in the "Sources" folder to your project. 169 | 170 | ### Use 171 | 172 | #### Storyboard 173 | 174 | Add a custom view to the storyboard and set its class to "M13Checkbox". Customize the available parameters in the attributes inspector as needed. 175 | 176 | **Note:** A shim was added to add support for setting enum properties through interface builder. Just retrieve the integer value corresponding to the enum value needed, and enter that into the property in the attributes inspector. 177 | 178 | #### Programmatically 179 | 180 | Just initialize the checkbox like one would initialize a UIView, and add it as a subview to your view hierarchy. 181 | 182 | ``` 183 | let checkbox = M13Checkbox(frame: CGRect(x: 0.0, y: 0.0, width: 40.0, height: 40.0)) 184 | view.addSubview(checkbox) 185 | ``` 186 | 187 |
188 | 189 | ## Project Structure 190 | 191 | **M13Checkbox** 192 | The main interface for M13Checkbox is the `M13Checkbox` class. It is a subclass of `UIControl` and handles the configurable properties, as well as touch events. 193 | 194 | **M13CheckboxController** 195 | Each `M13Checkbox` references an instance of `M13CheckboxController`, which controls the appearance and animations of its layers. The controller passes a set of layers to the `M13Checkbox`, which adds the layers to its layer hierarchy. The checkbox then asks the controller to perform the necessary animations on the layers to animate between states. Each animation type has its own subclass of `M13CheckboxController`. To add an animation, subclass `M13CheckboxController`, and add the animation type to the `Animation` enum, supporting `AnimationStyle` if applicable. Take a look at the existing controllers to see what variables and functions to override. 196 | 197 | **M13CheckboxAnimationGenerator** 198 | Each `M13CheckboxController` references an instance of `M13CheckboxAnimationGenerator`, which generates the animations that will be applied to layers during state transitions. The base class contains animations that are shared between multiple animation styles. An animation can subclass `M13CheckboxAnimationGenerator` to generate new animations specific to the animation type. 199 | 200 | **M13CheckboxPathGenerator** 201 | Each `M13CheckboxManager` references an instance of `M13CheckboxPathGenerator`, which generates the paths that will be displayed by the layers. The base class contains paths that are shared between multiple animation styles, as well as some boilerplate code to determine which path to use. Some animations have a subclass of `M13CheckboxPathGenerator` to add new paths specific to the animation type, or override existing paths to customize the look. 202 | 203 | `M13CheckboxPathGenerator` calculates the positions of the points of the checkmark with more than just a basic scaled offset. This allows the checkmark to always look the same, not matter what size the checkbox is. The math contained in the `checkmarkLongArmBoxIntersectionPoint` and `checkmarkLongArmEndPoint` are a simplified version of a human readable solution. To see the math that went into creating these equations, check out the "Math.nb" or the "Math.pdf" in the "Other" folder. 204 | 205 | **M13Checkbox+IB** 206 | A shim that gives the ability to set the enum values of `M13Checkbox` in Interface Builder. 207 | 208 |
209 | 210 | ## Project Details 211 | 212 | ### Requirements 213 | 214 | - iOS 8+ 215 | - Xcode 10.2+ 216 | - Swift 5 217 | 218 | ### Todo 219 | 220 | - Fix the animations between the checked and mixed states when the mark is a radio. When the circle is close to being flat, the left and right edges are not rounded, as well as render some artifacts. 221 | - Add visual feedback for UIControl's selected state. So that when the checkbox is touched, it animates slightly towards the new state. 222 | - Add support for interrupting animations mid-animation. So that if the checkbox is tapped multiple times in quick succession, it animates from the current values, instead of resetting the checkbox and restarting the animations. This might involve replacing CAAnimations with manually done animations using a CADisplayLink. Or the new UIViewPropertyAnimator. 223 | - tvOS support. 224 | - watchOS support. 225 | - macOS support. 226 | - Checkbox cells (Re-add label support) 227 | - Checkbox groups (single / multiple selection) 228 | 229 | ### License 230 | 231 | `M13Checkbox` is avaiable under [MIT Licence](https://github.com/Marxon13/M13Checkbox/blob/master/LICENSE). 232 | 233 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /Resources/Animation Duration.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Animation Duration.pdf -------------------------------------------------------------------------------- /Resources/Animation Style.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Animation Style.pdf -------------------------------------------------------------------------------- /Resources/Animation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Animation.pdf -------------------------------------------------------------------------------- /Resources/App Icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/App Icon.sketch -------------------------------------------------------------------------------- /Resources/Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Banner.png -------------------------------------------------------------------------------- /Resources/Banner.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Banner.sketch -------------------------------------------------------------------------------- /Resources/Banner@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Banner@2x.png -------------------------------------------------------------------------------- /Resources/Bounce.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Bounce.pdf -------------------------------------------------------------------------------- /Resources/Box Line Width.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Box Line Width.pdf -------------------------------------------------------------------------------- /Resources/Box Shape.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Box Shape.pdf -------------------------------------------------------------------------------- /Resources/Check Line Width.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Check Line Width.pdf -------------------------------------------------------------------------------- /Resources/Checkbox State.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Checkbox State.pdf -------------------------------------------------------------------------------- /Resources/Colors.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Colors.pdf -------------------------------------------------------------------------------- /Resources/Dot.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Dot.pdf -------------------------------------------------------------------------------- /Resources/Expand.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Expand.pdf -------------------------------------------------------------------------------- /Resources/Fade.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Fade.pdf -------------------------------------------------------------------------------- /Resources/Fill.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Fill.pdf -------------------------------------------------------------------------------- /Resources/Flat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Flat.pdf -------------------------------------------------------------------------------- /Resources/Icon/Icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-29.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-29@2x.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-29@3x.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-40.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-40@2x.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-40@3x.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-60.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-60@2x.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-60@3x.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-76.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-76@2x.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-76@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-76@3x.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-83.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-83.5.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-83.5@2x.png -------------------------------------------------------------------------------- /Resources/Icon/Icon-83.5@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/Icon-83.5@3x.png -------------------------------------------------------------------------------- /Resources/Icon/iTunesArtwork-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/iTunesArtwork-512.png -------------------------------------------------------------------------------- /Resources/Icon/iTunesArtwork-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icon/iTunesArtwork-512@2x.png -------------------------------------------------------------------------------- /Resources/Icons 2.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Icons 2.sketch -------------------------------------------------------------------------------- /Resources/Mark Type.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Mark Type.pdf -------------------------------------------------------------------------------- /Resources/Morph.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Morph.pdf -------------------------------------------------------------------------------- /Resources/Samples/Bounce Fill Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Bounce Fill Sample.gif -------------------------------------------------------------------------------- /Resources/Samples/Bounce Stroke Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Bounce Stroke Sample.gif -------------------------------------------------------------------------------- /Resources/Samples/Dot Fill Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Dot Fill Sample.gif -------------------------------------------------------------------------------- /Resources/Samples/Dot Stroke Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Dot Stroke Sample.gif -------------------------------------------------------------------------------- /Resources/Samples/Expand Fill Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Expand Fill Sample.gif -------------------------------------------------------------------------------- /Resources/Samples/Expand Stroke Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Expand Stroke Sample.gif -------------------------------------------------------------------------------- /Resources/Samples/Fade Fill Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Fade Fill Sample.gif -------------------------------------------------------------------------------- /Resources/Samples/Fade Stroke Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Fade Stroke Sample.gif -------------------------------------------------------------------------------- /Resources/Samples/Fill Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Fill Sample.gif -------------------------------------------------------------------------------- /Resources/Samples/Flat Fill Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Flat Fill Sample.gif -------------------------------------------------------------------------------- /Resources/Samples/Flat Stroke Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Flat Stroke Sample.gif -------------------------------------------------------------------------------- /Resources/Samples/Spiral Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Spiral Sample.gif -------------------------------------------------------------------------------- /Resources/Samples/Stroke Sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Samples/Stroke Sample.gif -------------------------------------------------------------------------------- /Resources/Spiral.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Spiral.pdf -------------------------------------------------------------------------------- /Resources/Stroke.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxon13/M13Checkbox/96ff6c8862db87095315480a56dcebe51e15dac8/Resources/Stroke.pdf -------------------------------------------------------------------------------- /Sources/DefaultValues.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConstantValues.swift 3 | // M13Checkbox 4 | // 5 | // Created by Andrea Antonioni on 30/07/17. 6 | // Copyright © 2017 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import Foundation 15 | 16 | // A set of default values used to initialize the object 17 | struct DefaultValues { 18 | 19 | static let animation: M13Checkbox.Animation = .stroke 20 | static let markType: M13Checkbox.MarkType = .checkmark 21 | static let boxType: M13Checkbox.BoxType = .circle 22 | static let checkState: M13Checkbox.CheckState = .unchecked 23 | static let controller: M13CheckboxController = M13CheckboxStrokeController() 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/M13Checkbox+IB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13Checkbox+IB.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 2/24/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | public extension M13Checkbox { 17 | 18 | /// A proxy to set the box type compatible with interface builder. 19 | @IBInspectable var _IBStateChangeAnimation: String { 20 | get { 21 | return stateChangeAnimation.rawValue 22 | } 23 | set { 24 | if let type = Animation(rawValue: newValue) { 25 | stateChangeAnimation = type 26 | } else { 27 | stateChangeAnimation = DefaultValues.animation 28 | } 29 | } 30 | } 31 | 32 | /// A proxy to set the mark type compatible with interface builder. 33 | @IBInspectable var _IBMarkType: String { 34 | get { 35 | return markType.rawValue 36 | } 37 | set { 38 | if let type = MarkType(rawValue: newValue) { 39 | markType = type 40 | } else { 41 | markType = DefaultValues.markType 42 | } 43 | } 44 | } 45 | 46 | /// A proxy to set the box type compatible with interface builder. 47 | @IBInspectable var _IBBoxType: String { 48 | get { 49 | return boxType.rawValue 50 | } 51 | set { 52 | if let type = BoxType(rawValue: newValue) { 53 | boxType = type 54 | } else { 55 | boxType = DefaultValues.boxType 56 | } 57 | } 58 | } 59 | 60 | /// A proxy to set the check state compatible with interface builder. 61 | @IBInspectable var _IBCheckState: String { 62 | get { 63 | return checkState.rawValue 64 | } 65 | set { 66 | if let temp = CheckState(rawValue: newValue) { 67 | checkState = temp 68 | } else { 69 | checkState = DefaultValues.checkState 70 | } 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Sources/M13CheckboxAnimationGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxAnimationGenerator.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 3/27/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxAnimationGenerator { 17 | 18 | //---------------------------- 19 | // MARK: - Properties 20 | //---------------------------- 21 | 22 | // The duration of animations that are generated by the animation manager. 23 | var animationDuration: TimeInterval = 0.3 24 | 25 | // The frame rate for certian keyframe animations. 26 | fileprivate var frameRate: CGFloat = 60.0 27 | 28 | //---------------------------- 29 | // MARK: - Quick Animations 30 | //---------------------------- 31 | 32 | final func quickAnimation(_ key: String, reverse: Bool) -> CABasicAnimation { 33 | let animation = CABasicAnimation(keyPath: key) 34 | // Set the start and end. 35 | if !reverse { 36 | animation.fromValue = 0.0 37 | animation.toValue = 1.0 38 | animation.timingFunction = CAMediaTimingFunction(name: .easeIn) 39 | } else { 40 | animation.fromValue = 1.0 41 | animation.toValue = 0.0 42 | animation.beginTime = CACurrentMediaTime() + (animationDuration * 0.9) 43 | animation.timingFunction = CAMediaTimingFunction(name: .easeOut) 44 | } 45 | // Set animation properties. 46 | animation.duration = animationDuration / 10.0 47 | animation.isRemovedOnCompletion = false 48 | animation.fillMode = CAMediaTimingFillMode.forwards 49 | 50 | return animation 51 | } 52 | 53 | /** 54 | Creates an animation that either quickly fades a layer in or out. 55 | - note: Mainly used to smooth out the start and end of various animations. 56 | - parameter reverse: The direction of the animation. 57 | - returns: A `CABasicAnimation` that animates the opacity property. 58 | */ 59 | final func quickOpacityAnimation(_ reverse: Bool) -> CABasicAnimation { 60 | return quickAnimation("opacity", reverse: reverse) 61 | } 62 | 63 | /** 64 | Creates an animation that either quickly changes the line width of a layer from 0% to 100%. 65 | - note: Mainly used to smooth out the start and end of various animations. 66 | - parameter reverse: The direction of the animation. 67 | - returns: A `CABasicAnimation` that animates the opacity property. 68 | */ 69 | final func quickLineWidthAnimation(_ width: CGFloat, reverse: Bool) -> CABasicAnimation { 70 | let animation = quickAnimation("lineWidth", reverse: reverse) 71 | // Set the start and end. 72 | if !reverse { 73 | animation.toValue = width 74 | } else { 75 | animation.fromValue = width 76 | } 77 | return animation 78 | } 79 | 80 | //---------------------------- 81 | // MARK: - Animation Component Generation 82 | //---------------------------- 83 | 84 | final func animation(_ key: String, reverse: Bool) -> CABasicAnimation { 85 | let animation = CABasicAnimation(keyPath: key) 86 | // Set the start and end. 87 | if !reverse { 88 | animation.fromValue = 0.0 89 | animation.toValue = 1.0 90 | } else { 91 | animation.fromValue = 1.0 92 | animation.toValue = 0.0 93 | } 94 | // Set animation properties. 95 | animation.duration = animationDuration 96 | animation.isRemovedOnCompletion = false 97 | animation.fillMode = CAMediaTimingFillMode.forwards 98 | animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) 99 | 100 | return animation 101 | } 102 | 103 | /** 104 | Creates an animation that animates the stroke property. 105 | - parameter reverse: The direction of the animation. 106 | - returns: A `CABasicAnimation` that animates the stroke property. 107 | */ 108 | final func strokeAnimation(_ reverse: Bool) -> CABasicAnimation { 109 | return animation("strokeEnd", reverse: reverse) 110 | } 111 | 112 | /** 113 | Creates an animation that animates the opacity property. 114 | - parameter reverse: The direction of the animation. 115 | - returns: A `CABasicAnimation` that animates the opacity property. 116 | */ 117 | final func opacityAnimation(_ reverse: Bool) -> CABasicAnimation { 118 | return animation("opacity", reverse: reverse) 119 | } 120 | 121 | /** 122 | Creates an animation that animates between two `UIBezierPath`s. 123 | - parameter fromPath: The start path. 124 | - parameter toPath: The end path. 125 | - returns: A `CABasicAnimation` that animates a path between the `fromPath` and `toPath`. 126 | */ 127 | final func morphAnimation(_ fromPath: UIBezierPath?, toPath: UIBezierPath?) -> CABasicAnimation { 128 | let animation = CABasicAnimation(keyPath: "path") 129 | // Set the start and end. 130 | animation.fromValue = fromPath?.cgPath 131 | animation.toValue = toPath?.cgPath 132 | // Set animation properties. 133 | animation.duration = animationDuration 134 | animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) 135 | animation.fillMode = CAMediaTimingFillMode.forwards 136 | animation.isRemovedOnCompletion = false 137 | 138 | return animation 139 | } 140 | 141 | /** 142 | Creates an animation that animates between a filled an unfilled box. 143 | - parameter numberOfBounces: The number of bounces in the animation. 144 | - parameter amplitude: The distance of the bounce. 145 | - parameter reverse: The direction of the animation. 146 | - returns: A `CAKeyframeAnimation` that animates a change in fill. 147 | */ 148 | final func fillAnimation(_ numberOfBounces: Int, amplitude: CGFloat, reverse: Bool) -> CAKeyframeAnimation { 149 | var values = [CATransform3D]() 150 | var keyTimes = [Float]() 151 | 152 | // Add the start scale 153 | if !reverse { 154 | values.append(CATransform3DMakeScale(0.0, 0.0, 0.0)) 155 | } else { 156 | values.append(CATransform3DMakeScale(1.0, 1.0, 1.0)) 157 | } 158 | keyTimes.append(0.0) 159 | 160 | // Add the bounces. 161 | if numberOfBounces > 0 { 162 | for i in 1...numberOfBounces { 163 | let scale = i % 2 == 1 ? (1.0 + (amplitude / CGFloat(i))) : (1.0 - (amplitude / CGFloat(i))) 164 | let time = (Float(i) * 1.0) / Float(numberOfBounces + 1) 165 | 166 | values.append(CATransform3DMakeScale(scale, scale, scale)) 167 | keyTimes.append(time) 168 | } 169 | } 170 | 171 | // Add the end scale. 172 | if !reverse { 173 | values.append(CATransform3DMakeScale(1.0, 1.0, 1.0)) 174 | } else { 175 | values.append(CATransform3DMakeScale(0.0001, 0.0001, 0.0001)) 176 | } 177 | keyTimes.append(1.0) 178 | 179 | // Create the animation. 180 | let animation = CAKeyframeAnimation(keyPath: "transform") 181 | animation.values = values.map({ NSValue(caTransform3D: $0) }) 182 | animation.keyTimes = keyTimes.map({ NSNumber(value: $0 as Float) }) 183 | animation.isRemovedOnCompletion = false 184 | animation.fillMode = CAMediaTimingFillMode.forwards 185 | animation.duration = animationDuration 186 | animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) 187 | 188 | return animation 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /Sources/M13CheckboxController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxController.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 3/18/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxController { 17 | 18 | //---------------------------- 19 | // MARK: - Properties 20 | //---------------------------- 21 | 22 | /// The path presets for the manager. 23 | var pathGenerator: M13CheckboxPathGenerator = M13CheckboxCheckPathGenerator() 24 | 25 | /// The animation presets for the manager. 26 | var animationGenerator: M13CheckboxAnimationGenerator = M13CheckboxAnimationGenerator() 27 | 28 | /// The current state of the checkbox. 29 | var state: M13Checkbox.CheckState = DefaultValues.checkState 30 | 31 | /// The current tint color. 32 | /// - Note: Subclasses should override didSet to update the layers when this value changes. 33 | var tintColor: UIColor = UIColor.black 34 | 35 | /// The secondary tint color. 36 | /// - Note: Subclasses should override didSet to update the layers when this value changes. 37 | var secondaryTintColor: UIColor? = UIColor.lightGray 38 | 39 | /// The secondary color of the mark. 40 | /// - Note: Subclasses should override didSet to update the layers when this value changes. 41 | var secondaryCheckmarkTintColor: UIColor? = UIColor.white 42 | 43 | /// Whether or not to hide the box. 44 | /// - Note: Subclasses should override didSet to update the layers when this value changes. 45 | var hideBox: Bool = false 46 | 47 | /// Whether or not to allow morphong between states. 48 | var enableMorphing: Bool = true 49 | 50 | // The type of mark to display. 51 | var markType: M13Checkbox.MarkType { 52 | get { 53 | return _markType 54 | } 55 | set { 56 | setMarkType(type: newValue, animated: false) 57 | } 58 | } 59 | 60 | private var _markType: M13Checkbox.MarkType = DefaultValues.markType 61 | 62 | func setMarkType(type: M13Checkbox.MarkType, animated: Bool) { 63 | guard type != _markType else { 64 | return 65 | } 66 | _setMarkType(type: type, animated: animated) 67 | _markType = type 68 | } 69 | 70 | private func _setMarkType(type: M13Checkbox.MarkType, animated: Bool) { 71 | var newPathGenerator: M13CheckboxPathGenerator 72 | switch type { 73 | case .checkmark: 74 | newPathGenerator = M13CheckboxCheckPathGenerator() 75 | case .radio: 76 | newPathGenerator = M13CheckboxRadioPathGenerator() 77 | case .addRemove: 78 | newPathGenerator = M13CheckboxAddRemovePathGenerator() 79 | case .disclosure: 80 | newPathGenerator = M13CheckboxDisclosurePathGenerator() 81 | } 82 | 83 | newPathGenerator.boxLineWidth = pathGenerator.boxLineWidth 84 | newPathGenerator.boxType = pathGenerator.boxType 85 | newPathGenerator.checkmarkLineWidth = pathGenerator.checkmarkLineWidth 86 | newPathGenerator.cornerRadius = pathGenerator.cornerRadius 87 | newPathGenerator.size = pathGenerator.size 88 | 89 | // Animate the change. 90 | if pathGenerator.pathForMark(state) != nil && animated { 91 | let previousState = state 92 | animate(state, toState: nil, completion: { [weak self] in 93 | self?.pathGenerator = newPathGenerator 94 | self?.resetLayersForState(previousState) 95 | if self?.pathGenerator.pathForMark(previousState) != nil { 96 | self?.animate(nil, toState: previousState) 97 | } 98 | }) 99 | } else if newPathGenerator.pathForMark(state) != nil && animated { 100 | let previousState = state 101 | pathGenerator = newPathGenerator 102 | resetLayersForState(nil) 103 | animate(nil, toState: previousState) 104 | } else { 105 | pathGenerator = newPathGenerator 106 | resetLayersForState(state) 107 | } 108 | } 109 | 110 | //---------------------------- 111 | // MARK: - Layers 112 | //---------------------------- 113 | 114 | /// The layers to display in the checkbox. The top layer is the last layer in the array. 115 | var layersToDisplay: [CALayer] { 116 | return [] 117 | } 118 | 119 | //---------------------------- 120 | // MARK: - Animations 121 | //---------------------------- 122 | 123 | /** 124 | Animates the layers between the two states. 125 | - parameter fromState: The previous state of the checkbox. 126 | - parameter toState: The new state of the checkbox. 127 | */ 128 | func animate(_ fromState: M13Checkbox.CheckState?, toState: M13Checkbox.CheckState?, completion: (() -> Void)? = nil) { 129 | if let toState = toState { 130 | state = toState 131 | } 132 | } 133 | 134 | //---------------------------- 135 | // MARK: - Layout 136 | //---------------------------- 137 | 138 | /// Layout the layers. 139 | func layoutLayers() { 140 | 141 | } 142 | 143 | //---------------------------- 144 | // MARK: - Display 145 | //---------------------------- 146 | 147 | /** 148 | Reset the layers to be in the given state. 149 | - parameter state: The new state of the checkbox. 150 | */ 151 | func resetLayersForState(_ state: M13Checkbox.CheckState?) { 152 | if let state = state { 153 | self.state = state 154 | } 155 | layoutLayers() 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /Sources/M13CheckboxGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxGestureRecognizer.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 4/12/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | import UIKit.UIGestureRecognizerSubclass 16 | 17 | internal class M13CheckboxGestureRecognizer: UILongPressGestureRecognizer { 18 | 19 | override init(target: Any?, action: Selector?) { 20 | super.init(target: target, action: action) 21 | // Set the minimium press duration to 0.0 to allow for basic taps. 22 | minimumPressDuration = 0.0 23 | } 24 | 25 | override func touchesEnded(_ touches: Set, with event: UIEvent) { 26 | // Check whether the touch is outside of the M13Checkbox's bounds, and fail to recognize if so. 27 | if let anyTouch = touches.first, let view = view { 28 | let touchPoint = anyTouch.location(in: view) 29 | if !view.bounds.contains(touchPoint) { 30 | state = .failed 31 | } 32 | } 33 | 34 | // If `self.state` is not yet set, the superclass implementation of this method will set it as it sees fit. 35 | super.touchesEnded(touches, with: event) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Managers/M13CheckboxBounceController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxBounceController.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 3/30/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxBounceController: M13CheckboxController { 17 | 18 | //---------------------------- 19 | // MARK: - Properties 20 | //---------------------------- 21 | 22 | override var tintColor: UIColor { 23 | didSet { 24 | selectedBoxLayer.strokeColor = tintColor.cgColor 25 | if style == .stroke { 26 | markLayer.strokeColor = tintColor.cgColor 27 | if markType == .radio { 28 | markLayer.fillColor = tintColor.cgColor 29 | } 30 | } else { 31 | selectedBoxLayer.fillColor = tintColor.cgColor 32 | } 33 | } 34 | } 35 | 36 | override var secondaryTintColor: UIColor? { 37 | didSet { 38 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 39 | } 40 | } 41 | 42 | override var secondaryCheckmarkTintColor: UIColor? { 43 | didSet { 44 | if style == .fill { 45 | markLayer.strokeColor = secondaryCheckmarkTintColor?.cgColor 46 | } 47 | } 48 | } 49 | 50 | override var hideBox: Bool { 51 | didSet { 52 | selectedBoxLayer.isHidden = hideBox 53 | unselectedBoxLayer.isHidden = hideBox 54 | } 55 | } 56 | 57 | fileprivate var style: M13Checkbox.AnimationStyle = .stroke 58 | 59 | init(style: M13Checkbox.AnimationStyle) { 60 | self.style = style 61 | super.init() 62 | sharedSetup() 63 | } 64 | 65 | override init() { 66 | super.init() 67 | sharedSetup() 68 | } 69 | 70 | fileprivate func sharedSetup() { 71 | // Disable som implicit animations. 72 | let newActions = [ 73 | "opacity": NSNull(), 74 | "strokeEnd": NSNull(), 75 | "transform": NSNull(), 76 | "fillColor": NSNull(), 77 | "path": NSNull(), 78 | "lineWidth": NSNull() 79 | ] 80 | 81 | // Setup the unselected box layer 82 | unselectedBoxLayer.lineCap = .round 83 | unselectedBoxLayer.rasterizationScale = UIScreen.main.scale 84 | unselectedBoxLayer.shouldRasterize = true 85 | unselectedBoxLayer.actions = newActions 86 | 87 | unselectedBoxLayer.opacity = 1.0 88 | unselectedBoxLayer.strokeEnd = 1.0 89 | unselectedBoxLayer.transform = CATransform3DIdentity 90 | unselectedBoxLayer.fillColor = nil 91 | 92 | // Setup the selected box layer. 93 | selectedBoxLayer.lineCap = .round 94 | selectedBoxLayer.rasterizationScale = UIScreen.main.scale 95 | selectedBoxLayer.shouldRasterize = true 96 | selectedBoxLayer.actions = newActions 97 | 98 | selectedBoxLayer.fillColor = nil 99 | selectedBoxLayer.transform = CATransform3DIdentity 100 | 101 | // Setup the checkmark layer. 102 | markLayer.lineCap = .round 103 | markLayer.lineJoin = .round 104 | markLayer.rasterizationScale = UIScreen.main.scale 105 | markLayer.shouldRasterize = true 106 | markLayer.actions = newActions 107 | 108 | markLayer.transform = CATransform3DIdentity 109 | markLayer.fillColor = nil 110 | } 111 | 112 | //---------------------------- 113 | // MARK: - Layers 114 | //---------------------------- 115 | 116 | let markLayer = CAShapeLayer() 117 | let selectedBoxLayer = CAShapeLayer() 118 | let unselectedBoxLayer = CAShapeLayer() 119 | 120 | override var layersToDisplay: [CALayer] { 121 | return [unselectedBoxLayer, selectedBoxLayer, markLayer] 122 | } 123 | 124 | //---------------------------- 125 | // MARK: - Animations 126 | //---------------------------- 127 | 128 | override func animate(_ fromState: M13Checkbox.CheckState?, toState: M13Checkbox.CheckState?, completion: (() -> Void)?) { 129 | super.animate(fromState, toState: toState) 130 | 131 | if pathGenerator.pathForMark(toState) == nil && pathGenerator.pathForMark(fromState) != nil { 132 | let amplitude: CGFloat = pathGenerator.boxType == .square ? 0.20 : 0.35 133 | let wiggleAnimation = animationGenerator.fillAnimation(1, amplitude: amplitude, reverse: true) 134 | let opacityAnimation = animationGenerator.opacityAnimation(true) 135 | opacityAnimation.duration = opacityAnimation.duration / 1.5 136 | opacityAnimation.beginTime = CACurrentMediaTime() + animationGenerator.animationDuration - opacityAnimation.duration 137 | 138 | CATransaction.begin() 139 | CATransaction.setCompletionBlock({ () -> Void in 140 | self.resetLayersForState(self.state) 141 | completion?() 142 | }) 143 | 144 | selectedBoxLayer.add(opacityAnimation, forKey: "opacity") 145 | markLayer.add(wiggleAnimation, forKey: "transform") 146 | 147 | CATransaction.commit() 148 | } else if pathGenerator.pathForMark(toState) != nil && pathGenerator.pathForMark(fromState) == nil { 149 | markLayer.path = pathGenerator.pathForMark(toState)?.cgPath 150 | 151 | let amplitude: CGFloat = pathGenerator.boxType == .square ? 0.20 : 0.35 152 | let wiggleAnimation = animationGenerator.fillAnimation(1, amplitude: amplitude, reverse: false) 153 | 154 | let opacityAnimation = animationGenerator.opacityAnimation(false) 155 | opacityAnimation.duration = opacityAnimation.duration / 1.5 156 | 157 | CATransaction.begin() 158 | CATransaction.setCompletionBlock({ () -> Void in 159 | self.resetLayersForState(self.state) 160 | completion?() 161 | }) 162 | 163 | selectedBoxLayer.add(opacityAnimation, forKey: "opacity") 164 | markLayer.add(wiggleAnimation, forKey: "transform") 165 | 166 | CATransaction.commit() 167 | } else { 168 | let fromPath = pathGenerator.pathForMark(fromState) 169 | let toPath = pathGenerator.pathForMark(toState) 170 | 171 | let morphAnimation = animationGenerator.morphAnimation(fromPath, toPath: toPath) 172 | 173 | CATransaction.begin() 174 | CATransaction.setCompletionBlock({ [weak self] () -> Void in 175 | self?.resetLayersForState(self?.state) 176 | completion?() 177 | }) 178 | 179 | markLayer.add(morphAnimation, forKey: "path") 180 | 181 | CATransaction.commit() 182 | } 183 | 184 | } 185 | 186 | //---------------------------- 187 | // MARK: - Layout 188 | //---------------------------- 189 | 190 | override func layoutLayers() { 191 | // Frames 192 | unselectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 193 | selectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 194 | markLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 195 | // Paths 196 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 197 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 198 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 199 | } 200 | 201 | //---------------------------- 202 | // MARK: - Display 203 | //---------------------------- 204 | 205 | override func resetLayersForState(_ state: M13Checkbox.CheckState?) { 206 | super.resetLayersForState(state) 207 | // Remove all remnant animations. They will interfere with each other if they are not removed before a new round of animations start. 208 | unselectedBoxLayer.removeAllAnimations() 209 | selectedBoxLayer.removeAllAnimations() 210 | markLayer.removeAllAnimations() 211 | 212 | // Set the properties for the final states of each necessary property of each layer. 213 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 214 | unselectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 215 | 216 | selectedBoxLayer.strokeColor = tintColor.cgColor 217 | selectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 218 | 219 | if style == .stroke { 220 | selectedBoxLayer.fillColor = nil 221 | markLayer.strokeColor = tintColor.cgColor 222 | if markType != .radio { 223 | markLayer.fillColor = nil 224 | } else { 225 | markLayer.fillColor = tintColor.cgColor 226 | } 227 | } else { 228 | selectedBoxLayer.fillColor = tintColor.cgColor 229 | markLayer.strokeColor = secondaryCheckmarkTintColor?.cgColor 230 | } 231 | 232 | markLayer.lineWidth = pathGenerator.checkmarkLineWidth 233 | 234 | if pathGenerator.pathForMark(state) != nil { 235 | markLayer.transform = CATransform3DIdentity 236 | selectedBoxLayer.opacity = 1.0 237 | } else { 238 | selectedBoxLayer.opacity = 0.0 239 | markLayer.transform = CATransform3DMakeScale(0.0, 0.0, 0.0) 240 | } 241 | 242 | // Paths 243 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 244 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 245 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 246 | } 247 | 248 | } 249 | 250 | 251 | -------------------------------------------------------------------------------- /Sources/Managers/M13CheckboxDotController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxDotController.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 4/1/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxDotController: M13CheckboxController { 17 | 18 | //---------------------------- 19 | // MARK: - Properties 20 | //---------------------------- 21 | 22 | override var tintColor: UIColor { 23 | didSet { 24 | selectedBoxLayer.strokeColor = tintColor.cgColor 25 | if style == .stroke { 26 | markLayer.strokeColor = tintColor.cgColor 27 | if markType == .radio { 28 | markLayer.fillColor = tintColor.cgColor 29 | } 30 | } else { 31 | selectedBoxLayer.fillColor = tintColor.cgColor 32 | } 33 | } 34 | } 35 | 36 | override var secondaryTintColor: UIColor? { 37 | didSet { 38 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 39 | } 40 | } 41 | 42 | override var secondaryCheckmarkTintColor: UIColor? { 43 | didSet { 44 | if style == .fill { 45 | markLayer.strokeColor = secondaryCheckmarkTintColor?.cgColor 46 | } 47 | } 48 | } 49 | 50 | override var hideBox: Bool { 51 | didSet { 52 | selectedBoxLayer.isHidden = hideBox 53 | unselectedBoxLayer.isHidden = hideBox 54 | } 55 | } 56 | 57 | fileprivate var style: M13Checkbox.AnimationStyle = .stroke 58 | 59 | init(style: M13Checkbox.AnimationStyle) { 60 | self.style = style 61 | super.init() 62 | sharedSetup() 63 | } 64 | 65 | override init() { 66 | super.init() 67 | sharedSetup() 68 | } 69 | 70 | fileprivate func sharedSetup() { 71 | // Disable som implicit animations. 72 | let newActions = [ 73 | "opacity": NSNull(), 74 | "strokeEnd": NSNull(), 75 | "transform": NSNull(), 76 | "fillColor": NSNull(), 77 | "path": NSNull(), 78 | "lineWidth": NSNull() 79 | ] 80 | 81 | // Setup the unselected box layer 82 | unselectedBoxLayer.lineCap = .round 83 | unselectedBoxLayer.rasterizationScale = UIScreen.main.scale 84 | unselectedBoxLayer.shouldRasterize = true 85 | unselectedBoxLayer.actions = newActions 86 | 87 | unselectedBoxLayer.transform = CATransform3DIdentity 88 | unselectedBoxLayer.fillColor = nil 89 | 90 | // Setup the selected box layer. 91 | selectedBoxLayer.lineCap = .round 92 | selectedBoxLayer.rasterizationScale = UIScreen.main.scale 93 | selectedBoxLayer.shouldRasterize = true 94 | selectedBoxLayer.actions = newActions 95 | 96 | selectedBoxLayer.fillColor = nil 97 | selectedBoxLayer.transform = CATransform3DIdentity 98 | 99 | // Setup the checkmark layer. 100 | markLayer.lineCap = .round 101 | markLayer.lineJoin = .round 102 | markLayer.rasterizationScale = UIScreen.main.scale 103 | markLayer.shouldRasterize = true 104 | markLayer.actions = newActions 105 | 106 | markLayer.transform = CATransform3DIdentity 107 | markLayer.fillColor = nil 108 | } 109 | 110 | //---------------------------- 111 | // MARK: - Layers 112 | //---------------------------- 113 | 114 | let markLayer = CAShapeLayer() 115 | let selectedBoxLayer = CAShapeLayer() 116 | let unselectedBoxLayer = CAShapeLayer() 117 | 118 | override var layersToDisplay: [CALayer] { 119 | return [unselectedBoxLayer, selectedBoxLayer, markLayer] 120 | } 121 | 122 | //---------------------------- 123 | // MARK: - Animations 124 | //---------------------------- 125 | 126 | override func animate(_ fromState: M13Checkbox.CheckState?, toState: M13Checkbox.CheckState?, completion: (() -> Void)?) { 127 | super.animate(fromState, toState: toState) 128 | 129 | if pathGenerator.pathForMark(toState) == nil && pathGenerator.pathForMark(fromState) != nil { 130 | let scaleAnimation = animationGenerator.fillAnimation(1, amplitude: 0.18, reverse: true) 131 | let opacityAnimation = animationGenerator.opacityAnimation(true) 132 | 133 | CATransaction.begin() 134 | CATransaction.setCompletionBlock({ () -> Void in 135 | self.resetLayersForState(toState) 136 | completion?() 137 | }) 138 | 139 | if style == .stroke { 140 | unselectedBoxLayer.opacity = 0.0 141 | let quickOpacityAnimation = animationGenerator.quickOpacityAnimation(false) 142 | quickOpacityAnimation.beginTime = CACurrentMediaTime() + scaleAnimation.duration - quickOpacityAnimation.duration 143 | unselectedBoxLayer.add(quickOpacityAnimation, forKey: "opacity") 144 | } 145 | selectedBoxLayer.add(scaleAnimation, forKey: "transform") 146 | markLayer.add(opacityAnimation, forKey: "opacity") 147 | 148 | CATransaction.commit() 149 | } else if pathGenerator.pathForMark(toState) != nil && pathGenerator.pathForMark(fromState) == nil { 150 | markLayer.path = pathGenerator.pathForMark(toState)?.cgPath 151 | 152 | let scaleAnimation = animationGenerator.fillAnimation(1, amplitude: 0.18, reverse: false) 153 | let opacityAnimation = animationGenerator.opacityAnimation(false) 154 | 155 | CATransaction.begin() 156 | CATransaction.setCompletionBlock({ () -> Void in 157 | self.resetLayersForState(toState) 158 | completion?() 159 | }) 160 | 161 | if style == .stroke { 162 | let quickOpacityAnimation = animationGenerator.quickOpacityAnimation(true) 163 | quickOpacityAnimation.beginTime = CACurrentMediaTime() 164 | unselectedBoxLayer.add(quickOpacityAnimation, forKey: "opacity") 165 | } 166 | selectedBoxLayer.add(scaleAnimation, forKey: "transform") 167 | markLayer.add(opacityAnimation, forKey: "opacity") 168 | 169 | CATransaction.commit() 170 | } else { 171 | let fromPath = pathGenerator.pathForMark(fromState) 172 | let toPath = pathGenerator.pathForMark(toState) 173 | 174 | let morphAnimation = animationGenerator.morphAnimation(fromPath, toPath: toPath) 175 | 176 | CATransaction.begin() 177 | CATransaction.setCompletionBlock({ [weak self] () -> Void in 178 | self?.resetLayersForState(self?.state) 179 | completion?() 180 | }) 181 | 182 | markLayer.add(morphAnimation, forKey: "path") 183 | 184 | CATransaction.commit() 185 | } 186 | } 187 | 188 | //---------------------------- 189 | // MARK: - Layout 190 | //---------------------------- 191 | 192 | override func layoutLayers() { 193 | // Frames 194 | unselectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 195 | selectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 196 | markLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 197 | // Paths 198 | unselectedBoxLayer.path = pathGenerator.pathForDot()?.cgPath 199 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 200 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 201 | } 202 | 203 | //---------------------------- 204 | // MARK: - Display 205 | //---------------------------- 206 | 207 | override func resetLayersForState(_ state: M13Checkbox.CheckState?) { 208 | super.resetLayersForState(state) 209 | // Remove all remnant animations. They will interfere with each other if they are not removed before a new round of animations start. 210 | unselectedBoxLayer.removeAllAnimations() 211 | selectedBoxLayer.removeAllAnimations() 212 | markLayer.removeAllAnimations() 213 | 214 | // Set the properties for the final states of each necessary property of each layer. 215 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 216 | unselectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 217 | 218 | selectedBoxLayer.strokeColor = tintColor.cgColor 219 | selectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 220 | 221 | if style == .stroke { 222 | selectedBoxLayer.fillColor = nil 223 | markLayer.strokeColor = tintColor.cgColor 224 | if markType != .radio { 225 | markLayer.fillColor = nil 226 | } else { 227 | markLayer.fillColor = tintColor.cgColor 228 | } 229 | } else { 230 | selectedBoxLayer.fillColor = tintColor.cgColor 231 | markLayer.strokeColor = secondaryCheckmarkTintColor?.cgColor 232 | } 233 | 234 | markLayer.lineWidth = pathGenerator.checkmarkLineWidth 235 | 236 | if pathGenerator.pathForMark(state) != nil { 237 | unselectedBoxLayer.opacity = 0.0 238 | selectedBoxLayer.transform = CATransform3DIdentity 239 | markLayer.opacity = 1.0 240 | } else { 241 | unselectedBoxLayer.opacity = 1.0 242 | selectedBoxLayer.transform = CATransform3DMakeScale(0.0, 0.0, 0.0) 243 | markLayer.opacity = 0.0 244 | } 245 | 246 | // Paths 247 | unselectedBoxLayer.path = pathGenerator.pathForDot()?.cgPath 248 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 249 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 250 | } 251 | 252 | } 253 | 254 | -------------------------------------------------------------------------------- /Sources/Managers/M13CheckboxExpandController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxExpandManager.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 4/1/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxExpandController: M13CheckboxController { 17 | 18 | //---------------------------- 19 | // MARK: - Properties 20 | //---------------------------- 21 | 22 | override var tintColor: UIColor { 23 | didSet { 24 | selectedBoxLayer.strokeColor = tintColor.cgColor 25 | if style == .stroke { 26 | markLayer.strokeColor = tintColor.cgColor 27 | if markType == .radio { 28 | markLayer.fillColor = tintColor.cgColor 29 | } 30 | } else { 31 | selectedBoxLayer.fillColor = tintColor.cgColor 32 | } 33 | } 34 | } 35 | 36 | override var secondaryTintColor: UIColor? { 37 | didSet { 38 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 39 | } 40 | } 41 | 42 | override var secondaryCheckmarkTintColor: UIColor? { 43 | didSet { 44 | if style == .fill { 45 | markLayer.strokeColor = secondaryCheckmarkTintColor?.cgColor 46 | } 47 | } 48 | } 49 | 50 | override var hideBox: Bool { 51 | didSet { 52 | selectedBoxLayer.isHidden = hideBox 53 | unselectedBoxLayer.isHidden = hideBox 54 | } 55 | } 56 | 57 | fileprivate var style: M13Checkbox.AnimationStyle = .stroke 58 | 59 | init(style: M13Checkbox.AnimationStyle) { 60 | self.style = style 61 | super.init() 62 | sharedSetup() 63 | } 64 | 65 | override init() { 66 | super.init() 67 | sharedSetup() 68 | } 69 | 70 | fileprivate func sharedSetup() { 71 | // Disable som implicit animations. 72 | let newActions = [ 73 | "opacity": NSNull(), 74 | "strokeEnd": NSNull(), 75 | "transform": NSNull(), 76 | "fillColor": NSNull(), 77 | "path": NSNull(), 78 | "lineWidth": NSNull() 79 | ] 80 | 81 | // Setup the unselected box layer 82 | unselectedBoxLayer.lineCap = .round 83 | unselectedBoxLayer.rasterizationScale = UIScreen.main.scale 84 | unselectedBoxLayer.shouldRasterize = true 85 | unselectedBoxLayer.actions = newActions 86 | 87 | unselectedBoxLayer.transform = CATransform3DIdentity 88 | unselectedBoxLayer.fillColor = nil 89 | 90 | // Setup the selected box layer. 91 | selectedBoxLayer.lineCap = .round 92 | selectedBoxLayer.rasterizationScale = UIScreen.main.scale 93 | selectedBoxLayer.shouldRasterize = true 94 | selectedBoxLayer.actions = newActions 95 | 96 | selectedBoxLayer.fillColor = nil 97 | selectedBoxLayer.transform = CATransform3DIdentity 98 | 99 | // Setup the checkmark layer. 100 | markLayer.lineCap = .round 101 | markLayer.lineJoin = .round 102 | markLayer.rasterizationScale = UIScreen.main.scale 103 | markLayer.shouldRasterize = true 104 | markLayer.actions = newActions 105 | 106 | markLayer.transform = CATransform3DIdentity 107 | markLayer.fillColor = nil 108 | } 109 | 110 | //---------------------------- 111 | // MARK: - Layers 112 | //---------------------------- 113 | 114 | let markLayer = CAShapeLayer() 115 | let selectedBoxLayer = CAShapeLayer() 116 | let unselectedBoxLayer = CAShapeLayer() 117 | 118 | override var layersToDisplay: [CALayer] { 119 | return [unselectedBoxLayer, selectedBoxLayer, markLayer] 120 | } 121 | 122 | //---------------------------- 123 | // MARK: - Animations 124 | //---------------------------- 125 | 126 | override func animate(_ fromState: M13Checkbox.CheckState?, toState: M13Checkbox.CheckState?, completion: (() -> Void)?) { 127 | super.animate(fromState, toState: toState) 128 | 129 | if pathGenerator.pathForMark(toState) == nil && pathGenerator.pathForMark(fromState) != nil { 130 | let amplitude: CGFloat = pathGenerator.boxType == .square ? 0.20 : 0.35 131 | let wiggleAnimation = animationGenerator.fillAnimation(1, amplitude: amplitude, reverse: true) 132 | 133 | CATransaction.begin() 134 | CATransaction.setCompletionBlock({ () -> Void in 135 | self.resetLayersForState(self.state) 136 | completion?() 137 | }) 138 | 139 | selectedBoxLayer.add(wiggleAnimation, forKey: "transform") 140 | markLayer.add(wiggleAnimation, forKey: "transform") 141 | 142 | CATransaction.commit() 143 | } else if pathGenerator.pathForMark(toState) != nil && pathGenerator.pathForMark(fromState) == nil { 144 | markLayer.path = pathGenerator.pathForMark(toState)?.cgPath 145 | 146 | let amplitude: CGFloat = pathGenerator.boxType == .square ? 0.20 : 0.35 147 | let wiggleAnimation = animationGenerator.fillAnimation(1, amplitude: amplitude, reverse: false) 148 | 149 | CATransaction.begin() 150 | CATransaction.setCompletionBlock({ () -> Void in 151 | self.resetLayersForState(self.state) 152 | completion?() 153 | }) 154 | 155 | selectedBoxLayer.add(wiggleAnimation, forKey: "transform") 156 | markLayer.add(wiggleAnimation, forKey: "transform") 157 | 158 | CATransaction.commit() 159 | } else { 160 | let fromPath = pathGenerator.pathForMark(fromState) 161 | let toPath = pathGenerator.pathForMark(toState) 162 | 163 | let morphAnimation = animationGenerator.morphAnimation(fromPath, toPath: toPath) 164 | 165 | CATransaction.begin() 166 | CATransaction.setCompletionBlock({ [weak self] () -> Void in 167 | self?.resetLayersForState(self?.state) 168 | completion?() 169 | }) 170 | 171 | markLayer.add(morphAnimation, forKey: "path") 172 | 173 | CATransaction.commit() 174 | } 175 | 176 | } 177 | 178 | //---------------------------- 179 | // MARK: - Layout 180 | //---------------------------- 181 | 182 | override func layoutLayers() { 183 | // Frames 184 | unselectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 185 | selectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 186 | markLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 187 | // Paths 188 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 189 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 190 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 191 | } 192 | 193 | //---------------------------- 194 | // MARK: - Display 195 | //---------------------------- 196 | 197 | override func resetLayersForState(_ state: M13Checkbox.CheckState?) { 198 | super.resetLayersForState(state) 199 | // Remove all remnant animations. They will interfere with each other if they are not removed before a new round of animations start. 200 | unselectedBoxLayer.removeAllAnimations() 201 | selectedBoxLayer.removeAllAnimations() 202 | markLayer.removeAllAnimations() 203 | 204 | // Set the properties for the final states of each necessary property of each layer. 205 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 206 | unselectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 207 | 208 | selectedBoxLayer.strokeColor = tintColor.cgColor 209 | selectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 210 | 211 | if style == .stroke { 212 | selectedBoxLayer.fillColor = nil 213 | markLayer.strokeColor = tintColor.cgColor 214 | if markType != .radio { 215 | markLayer.fillColor = nil 216 | } else { 217 | markLayer.fillColor = tintColor.cgColor 218 | } 219 | } else { 220 | selectedBoxLayer.fillColor = tintColor.cgColor 221 | markLayer.strokeColor = secondaryCheckmarkTintColor?.cgColor 222 | } 223 | 224 | markLayer.lineWidth = pathGenerator.checkmarkLineWidth 225 | 226 | if pathGenerator.pathForMark(state) != nil { 227 | markLayer.transform = CATransform3DIdentity 228 | selectedBoxLayer.transform = CATransform3DIdentity 229 | } else { 230 | markLayer.transform = CATransform3DMakeScale(0.0, 0.0, 0.0) 231 | selectedBoxLayer.transform = CATransform3DMakeScale(0.0, 0.0, 0.0) 232 | } 233 | 234 | // Paths 235 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 236 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 237 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 238 | } 239 | 240 | } 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /Sources/Managers/M13CheckboxFadeController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxFadeController.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 4/1/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxFadeController: M13CheckboxController { 17 | 18 | //---------------------------- 19 | // MARK: - Properties 20 | //---------------------------- 21 | 22 | override var tintColor: UIColor { 23 | didSet { 24 | selectedBoxLayer.strokeColor = tintColor.cgColor 25 | if style == .stroke { 26 | markLayer.strokeColor = tintColor.cgColor 27 | if markType == .radio { 28 | markLayer.fillColor = tintColor.cgColor 29 | } 30 | } else { 31 | selectedBoxLayer.fillColor = tintColor.cgColor 32 | } 33 | } 34 | } 35 | 36 | override var secondaryTintColor: UIColor? { 37 | didSet { 38 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 39 | } 40 | } 41 | 42 | override var secondaryCheckmarkTintColor: UIColor? { 43 | didSet { 44 | if style == .fill { 45 | markLayer.strokeColor = secondaryCheckmarkTintColor?.cgColor 46 | } 47 | } 48 | } 49 | 50 | override var hideBox: Bool { 51 | didSet { 52 | selectedBoxLayer.isHidden = hideBox 53 | unselectedBoxLayer.isHidden = hideBox 54 | } 55 | } 56 | 57 | fileprivate var style: M13Checkbox.AnimationStyle = .stroke 58 | 59 | init(style: M13Checkbox.AnimationStyle) { 60 | self.style = style 61 | super.init() 62 | sharedSetup() 63 | } 64 | 65 | override init() { 66 | super.init() 67 | sharedSetup() 68 | } 69 | 70 | fileprivate func sharedSetup() { 71 | // Disable som implicit animations. 72 | let newActions = [ 73 | "opacity": NSNull(), 74 | "strokeEnd": NSNull(), 75 | "transform": NSNull(), 76 | "fillColor": NSNull(), 77 | "path": NSNull(), 78 | "lineWidth": NSNull() 79 | ] 80 | 81 | // Setup the unselected box layer 82 | unselectedBoxLayer.lineCap = .round 83 | unselectedBoxLayer.rasterizationScale = UIScreen.main.scale 84 | unselectedBoxLayer.shouldRasterize = true 85 | unselectedBoxLayer.actions = newActions 86 | 87 | unselectedBoxLayer.opacity = 1.0 88 | unselectedBoxLayer.strokeEnd = 1.0 89 | unselectedBoxLayer.transform = CATransform3DIdentity 90 | unselectedBoxLayer.fillColor = nil 91 | 92 | // Setup the selected box layer. 93 | selectedBoxLayer.lineCap = .round 94 | selectedBoxLayer.rasterizationScale = UIScreen.main.scale 95 | selectedBoxLayer.shouldRasterize = true 96 | selectedBoxLayer.actions = newActions 97 | 98 | selectedBoxLayer.fillColor = nil 99 | selectedBoxLayer.transform = CATransform3DIdentity 100 | 101 | // Setup the checkmark layer. 102 | markLayer.lineCap = .round 103 | markLayer.lineJoin = .round 104 | markLayer.rasterizationScale = UIScreen.main.scale 105 | markLayer.shouldRasterize = true 106 | markLayer.actions = newActions 107 | 108 | markLayer.transform = CATransform3DIdentity 109 | markLayer.fillColor = nil 110 | } 111 | 112 | //---------------------------- 113 | // MARK: - Layers 114 | //---------------------------- 115 | 116 | let markLayer = CAShapeLayer() 117 | let selectedBoxLayer = CAShapeLayer() 118 | let unselectedBoxLayer = CAShapeLayer() 119 | 120 | override var layersToDisplay: [CALayer] { 121 | return [unselectedBoxLayer, selectedBoxLayer, markLayer] 122 | } 123 | 124 | //---------------------------- 125 | // MARK: - Animations 126 | //---------------------------- 127 | 128 | override func animate(_ fromState: M13Checkbox.CheckState?, toState: M13Checkbox.CheckState?, completion: (() -> Void)?) { 129 | super.animate(fromState, toState: toState) 130 | 131 | if pathGenerator.pathForMark(toState) == nil && pathGenerator.pathForMark(fromState) != nil { 132 | let opacityAnimation = animationGenerator.opacityAnimation(true) 133 | 134 | CATransaction.begin() 135 | CATransaction.setCompletionBlock({ () -> Void in 136 | self.resetLayersForState(self.state) 137 | completion?() 138 | }) 139 | 140 | selectedBoxLayer.add(opacityAnimation, forKey: "opacity") 141 | markLayer.add(opacityAnimation, forKey: "opacity") 142 | 143 | CATransaction.commit() 144 | } else if pathGenerator.pathForMark(toState) != nil && pathGenerator.pathForMark(fromState) == nil { 145 | markLayer.path = pathGenerator.pathForMark(toState)?.cgPath 146 | 147 | let opacityAnimation = animationGenerator.opacityAnimation(false) 148 | 149 | CATransaction.begin() 150 | CATransaction.setCompletionBlock({ () -> Void in 151 | self.resetLayersForState(self.state) 152 | completion?() 153 | }) 154 | 155 | selectedBoxLayer.add(opacityAnimation, forKey: "opacity") 156 | markLayer.add(opacityAnimation, forKey: "opacity") 157 | 158 | CATransaction.commit() 159 | } else { 160 | let fromPath = pathGenerator.pathForMark(fromState) 161 | let toPath = pathGenerator.pathForMark(toState) 162 | 163 | let morphAnimation = animationGenerator.morphAnimation(fromPath, toPath: toPath) 164 | 165 | CATransaction.begin() 166 | CATransaction.setCompletionBlock({ [weak self] () -> Void in 167 | self?.resetLayersForState(self?.state) 168 | completion?() 169 | }) 170 | 171 | markLayer.add(morphAnimation, forKey: "path") 172 | 173 | CATransaction.commit() 174 | } 175 | 176 | } 177 | 178 | //---------------------------- 179 | // MARK: - Layout 180 | //---------------------------- 181 | 182 | override func layoutLayers() { 183 | // Frames 184 | unselectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 185 | selectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 186 | markLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 187 | // Paths 188 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 189 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 190 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 191 | } 192 | 193 | //---------------------------- 194 | // MARK: - Display 195 | //---------------------------- 196 | 197 | override func resetLayersForState(_ state: M13Checkbox.CheckState?) { 198 | super.resetLayersForState(state) 199 | // Remove all remnant animations. They will interfere with each other if they are not removed before a new round of animations start. 200 | unselectedBoxLayer.removeAllAnimations() 201 | selectedBoxLayer.removeAllAnimations() 202 | markLayer.removeAllAnimations() 203 | 204 | // Set the properties for the final states of each necessary property of each layer. 205 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 206 | unselectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 207 | 208 | selectedBoxLayer.strokeColor = tintColor.cgColor 209 | selectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 210 | 211 | if style == .stroke { 212 | selectedBoxLayer.fillColor = nil 213 | markLayer.strokeColor = tintColor.cgColor 214 | if markType != .radio { 215 | markLayer.fillColor = nil 216 | } else { 217 | markLayer.fillColor = tintColor.cgColor 218 | } 219 | } else { 220 | selectedBoxLayer.fillColor = tintColor.cgColor 221 | markLayer.strokeColor = secondaryCheckmarkTintColor?.cgColor 222 | } 223 | 224 | markLayer.lineWidth = pathGenerator.checkmarkLineWidth 225 | 226 | if pathGenerator.pathForMark(state) != nil { 227 | markLayer.opacity = 1.0 228 | selectedBoxLayer.opacity = 1.0 229 | } else { 230 | selectedBoxLayer.opacity = 0.0 231 | markLayer.opacity = 0.0 232 | } 233 | 234 | // Paths 235 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 236 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 237 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /Sources/Managers/M13CheckboxFillController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxFillController.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 3/30/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxFillController: M13CheckboxController { 17 | 18 | //---------------------------- 19 | // MARK: - Properties 20 | //---------------------------- 21 | 22 | override var tintColor: UIColor { 23 | didSet { 24 | selectedBoxLayer.strokeColor = tintColor.cgColor 25 | selectedBoxLayer.fillColor = tintColor.cgColor 26 | } 27 | } 28 | 29 | override var secondaryTintColor: UIColor? { 30 | didSet { 31 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 32 | } 33 | } 34 | 35 | override var secondaryCheckmarkTintColor: UIColor? { 36 | didSet { 37 | markLayer.strokeColor = secondaryCheckmarkTintColor?.cgColor 38 | } 39 | } 40 | 41 | override var hideBox: Bool { 42 | didSet { 43 | selectedBoxLayer.isHidden = hideBox 44 | unselectedBoxLayer.isHidden = hideBox 45 | } 46 | } 47 | 48 | override init() { 49 | // Disable som implicit animations. 50 | let newActions = [ 51 | "opacity": NSNull(), 52 | "strokeEnd": NSNull(), 53 | "transform": NSNull(), 54 | "fillColor": NSNull(), 55 | "path": NSNull(), 56 | "lineWidth": NSNull() 57 | ] 58 | 59 | // Setup the unselected box layer 60 | unselectedBoxLayer.lineCap = .round 61 | unselectedBoxLayer.rasterizationScale = UIScreen.main.scale 62 | unselectedBoxLayer.shouldRasterize = true 63 | unselectedBoxLayer.actions = newActions 64 | 65 | unselectedBoxLayer.opacity = 1.0 66 | unselectedBoxLayer.strokeEnd = 1.0 67 | unselectedBoxLayer.transform = CATransform3DIdentity 68 | unselectedBoxLayer.fillColor = nil 69 | 70 | // Setup the selected box layer. 71 | selectedBoxLayer.lineCap = .round 72 | selectedBoxLayer.rasterizationScale = UIScreen.main.scale 73 | selectedBoxLayer.shouldRasterize = true 74 | selectedBoxLayer.actions = newActions 75 | 76 | selectedBoxLayer.transform = CATransform3DIdentity 77 | 78 | // Setup the checkmark layer. 79 | markLayer.lineCap = .round 80 | markLayer.lineJoin = .round 81 | markLayer.rasterizationScale = UIScreen.main.scale 82 | markLayer.shouldRasterize = true 83 | markLayer.actions = newActions 84 | 85 | markLayer.transform = CATransform3DIdentity 86 | markLayer.fillColor = nil 87 | } 88 | 89 | //---------------------------- 90 | // MARK: - Layers 91 | //---------------------------- 92 | 93 | let markLayer = CAShapeLayer() 94 | let selectedBoxLayer = CAShapeLayer() 95 | let unselectedBoxLayer = CAShapeLayer() 96 | 97 | override var layersToDisplay: [CALayer] { 98 | return [unselectedBoxLayer, selectedBoxLayer, markLayer] 99 | } 100 | 101 | //---------------------------- 102 | // MARK: - Animations 103 | //---------------------------- 104 | 105 | override func animate(_ fromState: M13Checkbox.CheckState?, toState: M13Checkbox.CheckState?, completion: (() -> Void)?) { 106 | super.animate(fromState, toState: toState) 107 | 108 | if pathGenerator.pathForMark(toState) == nil && pathGenerator.pathForMark(fromState) != nil { 109 | let wiggleAnimation = animationGenerator.fillAnimation(1, amplitude: 0.18, reverse: true) 110 | let opacityAnimation = animationGenerator.opacityAnimation(true) 111 | 112 | CATransaction.begin() 113 | CATransaction.setCompletionBlock({ () -> Void in 114 | self.resetLayersForState(toState) 115 | completion?() 116 | }) 117 | 118 | selectedBoxLayer.add(wiggleAnimation, forKey: "transform") 119 | markLayer.add(opacityAnimation, forKey: "opacity") 120 | 121 | CATransaction.commit() 122 | } else if pathGenerator.pathForMark(toState) != nil && pathGenerator.pathForMark(fromState) == nil { 123 | markLayer.path = pathGenerator.pathForMark(toState)?.cgPath 124 | 125 | let wiggleAnimation = animationGenerator.fillAnimation(1, amplitude: 0.18, reverse: false) 126 | let opacityAnimation = animationGenerator.opacityAnimation(false) 127 | 128 | CATransaction.begin() 129 | CATransaction.setCompletionBlock({ () -> Void in 130 | self.resetLayersForState(toState) 131 | completion?() 132 | }) 133 | 134 | selectedBoxLayer.add(wiggleAnimation, forKey: "transform") 135 | markLayer.add(opacityAnimation, forKey: "opacity") 136 | 137 | CATransaction.commit() 138 | } else { 139 | let fromPath = pathGenerator.pathForMark(fromState) 140 | let toPath = pathGenerator.pathForMark(toState) 141 | 142 | let morphAnimation = animationGenerator.morphAnimation(fromPath, toPath: toPath) 143 | 144 | CATransaction.begin() 145 | CATransaction.setCompletionBlock({ [weak self] () -> Void in 146 | self?.resetLayersForState(self?.state) 147 | completion?() 148 | }) 149 | 150 | markLayer.add(morphAnimation, forKey: "path") 151 | 152 | CATransaction.commit() 153 | } 154 | 155 | } 156 | 157 | //---------------------------- 158 | // MARK: - Layout 159 | //---------------------------- 160 | 161 | override func layoutLayers() { 162 | // Frames 163 | unselectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 164 | selectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 165 | markLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 166 | // Paths 167 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 168 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 169 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 170 | } 171 | 172 | //---------------------------- 173 | // MARK: - Display 174 | //---------------------------- 175 | 176 | override func resetLayersForState(_ state: M13Checkbox.CheckState?) { 177 | super.resetLayersForState(state) 178 | // Remove all remnant animations. They will interfere with each other if they are not removed before a new round of animations start. 179 | unselectedBoxLayer.removeAllAnimations() 180 | selectedBoxLayer.removeAllAnimations() 181 | markLayer.removeAllAnimations() 182 | 183 | // Set the properties for the final states of each necessary property of each layer. 184 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 185 | unselectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 186 | 187 | selectedBoxLayer.strokeColor = tintColor.cgColor 188 | selectedBoxLayer.fillColor = tintColor.cgColor 189 | selectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 190 | 191 | markLayer.strokeColor = secondaryCheckmarkTintColor?.cgColor 192 | markLayer.lineWidth = pathGenerator.checkmarkLineWidth 193 | markLayer.fillColor = nil 194 | 195 | if pathGenerator.pathForMark(state) != nil { 196 | selectedBoxLayer.transform = CATransform3DMakeScale(1.0, 1.0, 1.0) 197 | markLayer.opacity = 1.0 198 | } else { 199 | selectedBoxLayer.transform = CATransform3DMakeScale(0.0, 0.0, 0.0) 200 | markLayer.opacity = 0.0 201 | } 202 | 203 | // Paths 204 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 205 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 206 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 207 | } 208 | 209 | } 210 | 211 | -------------------------------------------------------------------------------- /Sources/Managers/M13CheckboxFlatController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxFlatController.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 4/1/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxFlatController: M13CheckboxController { 17 | 18 | //---------------------------- 19 | // MARK: - Properties 20 | //---------------------------- 21 | 22 | override var tintColor: UIColor { 23 | didSet { 24 | selectedBoxLayer.strokeColor = tintColor.cgColor 25 | if style == .stroke { 26 | markLayer.strokeColor = tintColor.cgColor 27 | if markType == .radio { 28 | markLayer.fillColor = tintColor.cgColor 29 | } 30 | } else { 31 | selectedBoxLayer.fillColor = tintColor.cgColor 32 | } 33 | } 34 | } 35 | 36 | override var secondaryTintColor: UIColor? { 37 | didSet { 38 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 39 | } 40 | } 41 | 42 | override var secondaryCheckmarkTintColor: UIColor? { 43 | didSet { 44 | if style == .fill { 45 | markLayer.strokeColor = secondaryCheckmarkTintColor?.cgColor 46 | } 47 | } 48 | } 49 | 50 | override var hideBox: Bool { 51 | didSet { 52 | selectedBoxLayer.isHidden = hideBox 53 | unselectedBoxLayer.isHidden = hideBox 54 | } 55 | } 56 | 57 | fileprivate var style: M13Checkbox.AnimationStyle = .stroke 58 | 59 | init(style: M13Checkbox.AnimationStyle) { 60 | self.style = style 61 | super.init() 62 | sharedSetup() 63 | } 64 | 65 | override init() { 66 | super.init() 67 | sharedSetup() 68 | } 69 | 70 | fileprivate func sharedSetup() { 71 | // Disable som implicit animations. 72 | let newActions = [ 73 | "opacity": NSNull(), 74 | "strokeEnd": NSNull(), 75 | "transform": NSNull(), 76 | "fillColor": NSNull(), 77 | "path": NSNull(), 78 | "lineWidth": NSNull() 79 | ] 80 | 81 | // Setup the unselected box layer 82 | unselectedBoxLayer.lineCap = .round 83 | unselectedBoxLayer.rasterizationScale = UIScreen.main.scale 84 | unselectedBoxLayer.shouldRasterize = true 85 | unselectedBoxLayer.actions = newActions 86 | 87 | unselectedBoxLayer.transform = CATransform3DIdentity 88 | unselectedBoxLayer.fillColor = nil 89 | 90 | // Setup the selected box layer. 91 | selectedBoxLayer.lineCap = .round 92 | selectedBoxLayer.rasterizationScale = UIScreen.main.scale 93 | selectedBoxLayer.shouldRasterize = true 94 | selectedBoxLayer.actions = newActions 95 | 96 | selectedBoxLayer.fillColor = nil 97 | selectedBoxLayer.transform = CATransform3DIdentity 98 | 99 | // Setup the checkmark layer. 100 | markLayer.lineCap = .round 101 | markLayer.lineJoin = .round 102 | markLayer.rasterizationScale = UIScreen.main.scale 103 | markLayer.shouldRasterize = true 104 | markLayer.actions = newActions 105 | 106 | markLayer.transform = CATransform3DIdentity 107 | markLayer.fillColor = nil 108 | } 109 | 110 | //---------------------------- 111 | // MARK: - Layers 112 | //---------------------------- 113 | 114 | let markLayer = CAShapeLayer() 115 | let selectedBoxLayer = CAShapeLayer() 116 | let unselectedBoxLayer = CAShapeLayer() 117 | 118 | override var layersToDisplay: [CALayer] { 119 | return [unselectedBoxLayer, selectedBoxLayer, markLayer] 120 | } 121 | 122 | //---------------------------- 123 | // MARK: - Animations 124 | //---------------------------- 125 | 126 | override func animate(_ fromState: M13Checkbox.CheckState?, toState: M13Checkbox.CheckState?, completion: (() -> Void)?) { 127 | super.animate(fromState, toState: toState) 128 | 129 | if pathGenerator.pathForMark(toState) == nil && pathGenerator.pathForMark(fromState) != nil { 130 | let morphAnimation = animationGenerator.morphAnimation(pathGenerator.pathForMark(), toPath: pathGenerator.pathForMixedMark()) 131 | morphAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) 132 | let opacityAnimation = animationGenerator.opacityAnimation(true) 133 | 134 | let quickOpacityAnimation = animationGenerator.quickOpacityAnimation(true) 135 | quickOpacityAnimation.duration = quickOpacityAnimation.duration * 4.0 136 | morphAnimation.duration = morphAnimation.duration - quickOpacityAnimation.duration 137 | quickOpacityAnimation.beginTime = CACurrentMediaTime() + morphAnimation.duration 138 | 139 | CATransaction.begin() 140 | CATransaction.setCompletionBlock({ () -> Void in 141 | self.resetLayersForState(toState) 142 | completion?() 143 | }) 144 | 145 | selectedBoxLayer.add(opacityAnimation, forKey: "opacity") 146 | if fromState != .mixed { 147 | markLayer.add(morphAnimation, forKey: "path") 148 | } 149 | markLayer.add(quickOpacityAnimation, forKey: "opacity") 150 | 151 | CATransaction.commit() 152 | } else if pathGenerator.pathForMark(toState) != nil && pathGenerator.pathForMark(fromState) == nil { 153 | markLayer.path = pathGenerator.pathForMixedMark()?.cgPath 154 | 155 | let morphAnimation = animationGenerator.morphAnimation(pathGenerator.pathForMixedMark(), toPath: pathGenerator.pathForMark()) 156 | morphAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) 157 | let opacityAnimation = animationGenerator.opacityAnimation(false) 158 | 159 | let quickOpacityAnimation = animationGenerator.quickOpacityAnimation(false) 160 | quickOpacityAnimation.duration = quickOpacityAnimation.duration * 4.0 161 | morphAnimation.beginTime = CACurrentMediaTime() + quickOpacityAnimation.duration 162 | morphAnimation.duration = morphAnimation.duration - quickOpacityAnimation.duration 163 | 164 | CATransaction.begin() 165 | CATransaction.setCompletionBlock({ () -> Void in 166 | self.resetLayersForState(toState) 167 | completion?() 168 | }) 169 | 170 | selectedBoxLayer.add(opacityAnimation, forKey: "opacity") 171 | if toState != .mixed { 172 | markLayer.add(morphAnimation, forKey: "path") 173 | } 174 | markLayer.add(quickOpacityAnimation, forKey: "opacity") 175 | 176 | CATransaction.commit() 177 | } else { 178 | let fromPath = pathGenerator.pathForMark(fromState) 179 | let toPath = pathGenerator.pathForMark(toState) 180 | 181 | let morphAnimation = animationGenerator.morphAnimation(fromPath, toPath: toPath) 182 | 183 | CATransaction.begin() 184 | CATransaction.setCompletionBlock({ [weak self] () -> Void in 185 | self?.resetLayersForState(self?.state) 186 | completion?() 187 | }) 188 | 189 | markLayer.add(morphAnimation, forKey: "path") 190 | 191 | CATransaction.commit() 192 | } 193 | 194 | } 195 | 196 | //---------------------------- 197 | // MARK: - Layout 198 | //---------------------------- 199 | 200 | override func layoutLayers() { 201 | // Frames 202 | unselectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 203 | selectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 204 | markLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 205 | // Paths 206 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 207 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 208 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 209 | } 210 | 211 | //---------------------------- 212 | // MARK: - Display 213 | //---------------------------- 214 | 215 | override func resetLayersForState(_ state: M13Checkbox.CheckState?) { 216 | super.resetLayersForState(state) 217 | // Remove all remnant animations. They will interfere with each other if they are not removed before a new round of animations start. 218 | unselectedBoxLayer.removeAllAnimations() 219 | selectedBoxLayer.removeAllAnimations() 220 | markLayer.removeAllAnimations() 221 | 222 | // Set the properties for the final states of each necessary property of each layer. 223 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 224 | unselectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 225 | 226 | selectedBoxLayer.strokeColor = tintColor.cgColor 227 | selectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 228 | 229 | if style == .stroke { 230 | selectedBoxLayer.fillColor = nil 231 | markLayer.strokeColor = tintColor.cgColor 232 | if markType != .radio { 233 | markLayer.fillColor = nil 234 | } else { 235 | markLayer.fillColor = tintColor.cgColor 236 | } 237 | } else { 238 | selectedBoxLayer.fillColor = tintColor.cgColor 239 | markLayer.strokeColor = secondaryCheckmarkTintColor?.cgColor 240 | } 241 | 242 | markLayer.lineWidth = pathGenerator.checkmarkLineWidth 243 | 244 | if pathGenerator.pathForMark(state) != nil { 245 | selectedBoxLayer.opacity = 1.0 246 | markLayer.opacity = 1.0 247 | } else { 248 | selectedBoxLayer.opacity = 0.0 249 | markLayer.opacity = 0.0 250 | } 251 | 252 | // Paths 253 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 254 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 255 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 256 | } 257 | 258 | } 259 | 260 | -------------------------------------------------------------------------------- /Sources/Managers/M13CheckboxSpiralController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxSpiralController.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 4/1/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxSpiralController: M13CheckboxController { 17 | 18 | //---------------------------- 19 | // MARK: - Properties 20 | //---------------------------- 21 | 22 | override var tintColor: UIColor { 23 | didSet { 24 | selectedBoxLayer.strokeColor = tintColor.cgColor 25 | markLayer.strokeColor = tintColor.cgColor 26 | } 27 | } 28 | 29 | override var secondaryTintColor: UIColor? { 30 | didSet { 31 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 32 | } 33 | } 34 | 35 | override var hideBox: Bool { 36 | didSet { 37 | selectedBoxLayer.isHidden = hideBox 38 | unselectedBoxLayer.isHidden = hideBox 39 | } 40 | } 41 | 42 | override init() { 43 | super.init() 44 | 45 | // Disable som implicit animations. 46 | let newActions = [ 47 | "opacity": NSNull(), 48 | "strokeEnd": NSNull(), 49 | "transform": NSNull(), 50 | "fillColor": NSNull(), 51 | "path": NSNull(), 52 | "lineWidth": NSNull() 53 | ] 54 | 55 | // Setup the unselected box layer 56 | unselectedBoxLayer.lineCap = .round 57 | unselectedBoxLayer.rasterizationScale = UIScreen.main.scale 58 | unselectedBoxLayer.shouldRasterize = true 59 | unselectedBoxLayer.actions = newActions 60 | 61 | unselectedBoxLayer.opacity = 1.0 62 | unselectedBoxLayer.strokeEnd = 1.0 63 | unselectedBoxLayer.transform = CATransform3DIdentity 64 | unselectedBoxLayer.fillColor = nil 65 | 66 | // Setup the selected box layer. 67 | selectedBoxLayer.lineCap = .round 68 | selectedBoxLayer.rasterizationScale = UIScreen.main.scale 69 | selectedBoxLayer.shouldRasterize = true 70 | selectedBoxLayer.actions = newActions 71 | 72 | selectedBoxLayer.transform = CATransform3DIdentity 73 | selectedBoxLayer.fillColor = nil 74 | 75 | // Setup the checkmark layer. 76 | markLayer.lineCap = .round 77 | markLayer.lineJoin = .round 78 | markLayer.rasterizationScale = UIScreen.main.scale 79 | markLayer.shouldRasterize = true 80 | markLayer.actions = newActions 81 | 82 | markLayer.transform = CATransform3DIdentity 83 | markLayer.fillColor = nil 84 | } 85 | 86 | //---------------------------- 87 | // MARK: - Layers 88 | //---------------------------- 89 | 90 | let markLayer = CAShapeLayer() 91 | let selectedBoxLayer = CAShapeLayer() 92 | let unselectedBoxLayer = CAShapeLayer() 93 | 94 | override var layersToDisplay: [CALayer] { 95 | return [unselectedBoxLayer, selectedBoxLayer, markLayer] 96 | } 97 | 98 | //---------------------------- 99 | // MARK: - Animations 100 | //---------------------------- 101 | 102 | override func animate(_ fromState: M13Checkbox.CheckState?, toState: M13Checkbox.CheckState?, completion: (() -> Void)?) { 103 | super.animate(fromState, toState: toState) 104 | 105 | if pathGenerator.pathForMark(toState) == nil && pathGenerator.pathForMark(fromState) != nil { 106 | // Temporarily set the path of the checkmark to the long checkmark 107 | markLayer.path = pathGenerator.pathForLongMark(fromState)?.reversing().cgPath 108 | 109 | let checkMorphAnimation = animationGenerator.morphAnimation(pathGenerator.pathForMark(fromState)?.reversing(), toPath: pathGenerator.pathForLongMark(fromState)?.reversing()) 110 | checkMorphAnimation.fillMode = .backwards 111 | checkMorphAnimation.duration = checkMorphAnimation.duration / 4.0 112 | checkMorphAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) 113 | 114 | let checkStrokeAnimation = animationGenerator.strokeAnimation(true) 115 | checkStrokeAnimation.beginTime = CACurrentMediaTime() + checkMorphAnimation.duration 116 | checkStrokeAnimation.duration = checkStrokeAnimation.duration / 4.0 117 | checkStrokeAnimation.timingFunction = CAMediaTimingFunction(name: .linear) 118 | 119 | let boxStrokeAnimation = animationGenerator.strokeAnimation(true) 120 | boxStrokeAnimation.beginTime = CACurrentMediaTime() + checkMorphAnimation.duration + checkStrokeAnimation.duration 121 | boxStrokeAnimation.duration = boxStrokeAnimation.duration / 2.0 122 | boxStrokeAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) 123 | 124 | let quickOpacityAnimation = animationGenerator.quickOpacityAnimation(true) 125 | 126 | let checkQuickOpacityAnimation = animationGenerator.quickOpacityAnimation(true) 127 | checkQuickOpacityAnimation.duration = 0.001 128 | checkQuickOpacityAnimation.beginTime = CACurrentMediaTime() + checkMorphAnimation.duration + checkStrokeAnimation.duration 129 | 130 | CATransaction.begin() 131 | CATransaction.setCompletionBlock({ () -> Void in 132 | self.resetLayersForState(toState) 133 | completion?() 134 | }) 135 | 136 | selectedBoxLayer.add(quickOpacityAnimation, forKey: "opacity") 137 | markLayer.add(checkMorphAnimation, forKey: "path") 138 | markLayer.add(checkStrokeAnimation, forKey: "strokeEnd") 139 | markLayer.add(checkQuickOpacityAnimation, forKey: "opacity") 140 | selectedBoxLayer.add(boxStrokeAnimation, forKey: "strokeEnd") 141 | 142 | markLayer.strokeEnd = CGFloat((checkStrokeAnimation.fromValue as! NSNumber).floatValue) 143 | markLayer.opacity = (checkQuickOpacityAnimation.fromValue as! NSNumber).floatValue 144 | selectedBoxLayer.strokeEnd = CGFloat((checkStrokeAnimation.fromValue as! NSNumber).floatValue) 145 | 146 | CATransaction.commit() 147 | } else if pathGenerator.pathForMark(toState) != nil && pathGenerator.pathForMark(fromState) == nil { 148 | // Temporarly set to the long mark. 149 | markLayer.path = pathGenerator.pathForLongMark(toState)?.reversing().cgPath 150 | 151 | let quickOpacityAnimation = animationGenerator.quickOpacityAnimation(false) 152 | 153 | let boxStrokeAnimation = animationGenerator.strokeAnimation(false) 154 | boxStrokeAnimation.duration = boxStrokeAnimation.duration / 2.0 155 | boxStrokeAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) 156 | 157 | let checkQuickOpacityAnimation = animationGenerator.quickOpacityAnimation(false) 158 | checkQuickOpacityAnimation.duration = 0.001 159 | checkQuickOpacityAnimation.beginTime = CACurrentMediaTime() + boxStrokeAnimation.duration 160 | 161 | let checkStrokeAnimation = animationGenerator.strokeAnimation(false) 162 | checkStrokeAnimation.duration = checkStrokeAnimation.duration / 4.0 163 | checkStrokeAnimation.timingFunction = CAMediaTimingFunction(name: .linear) 164 | checkStrokeAnimation.fillMode = .forwards 165 | checkStrokeAnimation.beginTime = CACurrentMediaTime() + boxStrokeAnimation.duration 166 | 167 | let checkMorphAnimation = animationGenerator.morphAnimation(pathGenerator.pathForLongMark(toState)?.reversing(), toPath: pathGenerator.pathForMark(toState)?.reversing()) 168 | checkMorphAnimation.duration = checkMorphAnimation.duration / 4.0 169 | checkMorphAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) 170 | checkMorphAnimation.beginTime = CACurrentMediaTime() + boxStrokeAnimation.duration + checkStrokeAnimation.duration 171 | 172 | CATransaction.begin() 173 | CATransaction.setCompletionBlock({ () -> Void in 174 | self.resetLayersForState(toState) 175 | completion?() 176 | }) 177 | 178 | selectedBoxLayer.add(quickOpacityAnimation, forKey: "opacity") 179 | selectedBoxLayer.add(boxStrokeAnimation, forKey: "strokeEnd") 180 | markLayer.add(checkQuickOpacityAnimation, forKey: "opacity") 181 | markLayer.add(checkStrokeAnimation, forKey: "strokeEnd") 182 | markLayer.add(checkMorphAnimation, forKey: "path") 183 | 184 | markLayer.strokeEnd = CGFloat((checkStrokeAnimation.fromValue as! NSNumber).floatValue) 185 | markLayer.opacity = (checkQuickOpacityAnimation.fromValue as! NSNumber).floatValue 186 | markLayer.path = pathGenerator.pathForLongMark(toState)?.reversing().cgPath 187 | 188 | CATransaction.commit() 189 | } else { 190 | let fromPath = pathGenerator.pathForMark(fromState) 191 | let toPath = pathGenerator.pathForMark(toState) 192 | 193 | let morphAnimation = animationGenerator.morphAnimation(fromPath, toPath: toPath) 194 | 195 | CATransaction.begin() 196 | CATransaction.setCompletionBlock({ [weak self] () -> Void in 197 | self?.resetLayersForState(self?.state) 198 | completion?() 199 | }) 200 | 201 | markLayer.add(morphAnimation, forKey: "path") 202 | 203 | CATransaction.commit() 204 | } 205 | } 206 | 207 | //---------------------------- 208 | // MARK: - Layout 209 | //---------------------------- 210 | 211 | override func layoutLayers() { 212 | // Frames 213 | unselectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 214 | selectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 215 | markLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 216 | // Paths 217 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 218 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 219 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 220 | } 221 | 222 | //---------------------------- 223 | // MARK: - Display 224 | //---------------------------- 225 | 226 | override func resetLayersForState(_ state: M13Checkbox.CheckState?) { 227 | super.resetLayersForState(state) 228 | // Remove all remnant animations. They will interfere with each other if they are not removed before a new round of animations start. 229 | unselectedBoxLayer.removeAllAnimations() 230 | selectedBoxLayer.removeAllAnimations() 231 | markLayer.removeAllAnimations() 232 | 233 | // Set the properties for the final states of each necessary property of each layer. 234 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 235 | unselectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 236 | unselectedBoxLayer.fillColor = nil 237 | 238 | selectedBoxLayer.strokeColor = tintColor.cgColor 239 | selectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 240 | 241 | markLayer.strokeColor = tintColor.cgColor 242 | markLayer.lineWidth = pathGenerator.checkmarkLineWidth 243 | markLayer.fillColor = nil 244 | 245 | if pathGenerator.pathForMark(state) != nil { 246 | selectedBoxLayer.opacity = 1.0 247 | selectedBoxLayer.strokeEnd = 1.0 248 | 249 | markLayer.opacity = 1.0 250 | markLayer.strokeEnd = 1.0 251 | } else { 252 | selectedBoxLayer.opacity = 0.0 253 | selectedBoxLayer.strokeEnd = 0.0 254 | 255 | markLayer.opacity = 0.0 256 | markLayer.strokeEnd = 0.0 257 | } 258 | 259 | // Paths 260 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 261 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 262 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 263 | } 264 | 265 | } 266 | 267 | -------------------------------------------------------------------------------- /Sources/Managers/M13CheckboxStrokeController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxStrokeController.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 3/27/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxStrokeController: M13CheckboxController { 17 | 18 | //---------------------------- 19 | // MARK: - Properties 20 | //---------------------------- 21 | 22 | override var tintColor: UIColor { 23 | didSet { 24 | selectedBoxLayer.strokeColor = tintColor.cgColor 25 | markLayer.strokeColor = tintColor.cgColor 26 | } 27 | } 28 | 29 | override var secondaryTintColor: UIColor? { 30 | didSet { 31 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 32 | } 33 | } 34 | 35 | override var hideBox: Bool { 36 | didSet { 37 | selectedBoxLayer.isHidden = hideBox 38 | unselectedBoxLayer.isHidden = hideBox 39 | } 40 | } 41 | 42 | override init() { 43 | // Disable som implicit animations. 44 | let newActions = [ 45 | "opacity": NSNull(), 46 | "strokeEnd": NSNull(), 47 | "transform": NSNull(), 48 | "fillColor": NSNull(), 49 | "path": NSNull(), 50 | "lineWidth": NSNull() 51 | ] 52 | 53 | // Setup the unselected box layer 54 | unselectedBoxLayer.lineCap = .round 55 | unselectedBoxLayer.rasterizationScale = UIScreen.main.scale 56 | unselectedBoxLayer.shouldRasterize = true 57 | unselectedBoxLayer.actions = newActions 58 | 59 | unselectedBoxLayer.opacity = 1.0 60 | unselectedBoxLayer.strokeEnd = 1.0 61 | unselectedBoxLayer.transform = CATransform3DIdentity 62 | unselectedBoxLayer.fillColor = nil 63 | 64 | // Setup the selected box layer. 65 | selectedBoxLayer.lineCap = .round 66 | selectedBoxLayer.rasterizationScale = UIScreen.main.scale 67 | selectedBoxLayer.shouldRasterize = true 68 | selectedBoxLayer.actions = newActions 69 | 70 | selectedBoxLayer.transform = CATransform3DIdentity 71 | selectedBoxLayer.fillColor = nil 72 | 73 | // Setup the checkmark layer. 74 | markLayer.lineCap = .round 75 | markLayer.lineJoin = .round 76 | markLayer.rasterizationScale = UIScreen.main.scale 77 | markLayer.shouldRasterize = true 78 | markLayer.actions = newActions 79 | 80 | markLayer.transform = CATransform3DIdentity 81 | markLayer.fillColor = nil 82 | } 83 | 84 | //---------------------------- 85 | // MARK: - Layers 86 | //---------------------------- 87 | 88 | let markLayer = CAShapeLayer() 89 | let selectedBoxLayer = CAShapeLayer() 90 | let unselectedBoxLayer = CAShapeLayer() 91 | 92 | override var layersToDisplay: [CALayer] { 93 | return [unselectedBoxLayer, selectedBoxLayer, markLayer] 94 | } 95 | 96 | //---------------------------- 97 | // MARK: - Animations 98 | //---------------------------- 99 | 100 | override func animate(_ fromState: M13Checkbox.CheckState?, toState: M13Checkbox.CheckState?, completion: (() -> Void)?) { 101 | super.animate(fromState, toState: toState, completion: completion) 102 | 103 | if pathGenerator.pathForMark(toState) == nil && pathGenerator.pathForMark(fromState) != nil { 104 | let strokeAnimation = animationGenerator.strokeAnimation(true) 105 | let quickOpacityAnimation = animationGenerator.quickOpacityAnimation(true) 106 | 107 | CATransaction.begin() 108 | CATransaction.setCompletionBlock({ [weak self] () -> Void in 109 | self?.resetLayersForState(self?.state) 110 | completion?() 111 | }) 112 | 113 | markLayer.add(strokeAnimation, forKey: "strokeEnd") 114 | markLayer.add(quickOpacityAnimation, forKey: "opacity") 115 | selectedBoxLayer.add(strokeAnimation, forKey: "strokeEnd") 116 | selectedBoxLayer.add(quickOpacityAnimation, forKey: "opacity") 117 | 118 | CATransaction.commit() 119 | } else if pathGenerator.pathForMark(toState) != nil && pathGenerator.pathForMark(fromState) == nil { 120 | markLayer.path = pathGenerator.pathForMark(toState)?.cgPath 121 | 122 | let strokeAnimation = animationGenerator.strokeAnimation(false) 123 | let quickOpacityAnimation = animationGenerator.quickOpacityAnimation(false) 124 | 125 | CATransaction.begin() 126 | CATransaction.setCompletionBlock({ [weak self] () -> Void in 127 | self?.resetLayersForState(self?.state) 128 | completion?() 129 | }) 130 | 131 | markLayer.add(strokeAnimation, forKey: "strokeEnd") 132 | markLayer.add(quickOpacityAnimation, forKey: "opacity") 133 | selectedBoxLayer.add(strokeAnimation, forKey: "strokeEnd") 134 | selectedBoxLayer.add(quickOpacityAnimation, forKey: "opacity") 135 | 136 | CATransaction.commit() 137 | } else { 138 | let fromPath = pathGenerator.pathForMark(fromState) 139 | let toPath = pathGenerator.pathForMark(toState) 140 | 141 | let morphAnimation = animationGenerator.morphAnimation(fromPath, toPath: toPath) 142 | 143 | CATransaction.begin() 144 | CATransaction.setCompletionBlock({ [weak self] () -> Void in 145 | self?.resetLayersForState(self?.state) 146 | completion?() 147 | }) 148 | 149 | markLayer.add(morphAnimation, forKey: "path") 150 | 151 | CATransaction.commit() 152 | } 153 | } 154 | 155 | //---------------------------- 156 | // MARK: - Layout 157 | //---------------------------- 158 | 159 | override func layoutLayers() { 160 | // Frames 161 | unselectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 162 | selectedBoxLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 163 | markLayer.frame = CGRect(x: 0.0, y: 0.0, width: pathGenerator.size, height: pathGenerator.size) 164 | // Paths 165 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 166 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 167 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 168 | } 169 | 170 | //---------------------------- 171 | // MARK: - Display 172 | //---------------------------- 173 | 174 | override func resetLayersForState(_ state: M13Checkbox.CheckState?) { 175 | super.resetLayersForState(state) 176 | // Remove all remnant animations. They will interfere with each other if they are not removed before a new round of animations start. 177 | unselectedBoxLayer.removeAllAnimations() 178 | selectedBoxLayer.removeAllAnimations() 179 | markLayer.removeAllAnimations() 180 | 181 | // Set the properties for the final states of each necessary property of each layer. 182 | unselectedBoxLayer.strokeColor = secondaryTintColor?.cgColor 183 | unselectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 184 | 185 | selectedBoxLayer.strokeColor = tintColor.cgColor 186 | selectedBoxLayer.lineWidth = pathGenerator.boxLineWidth 187 | selectedBoxLayer.fillColor = nil 188 | 189 | markLayer.strokeColor = tintColor.cgColor 190 | markLayer.lineWidth = pathGenerator.checkmarkLineWidth 191 | markLayer.fillColor = nil 192 | 193 | if pathGenerator.pathForMark(state) != nil { 194 | selectedBoxLayer.opacity = 1.0 195 | selectedBoxLayer.strokeEnd = 1.0 196 | 197 | markLayer.opacity = 1.0 198 | markLayer.strokeEnd = 1.0 199 | } else { 200 | selectedBoxLayer.opacity = 0.0 201 | selectedBoxLayer.strokeEnd = 0.0 202 | 203 | markLayer.opacity = 0.0 204 | markLayer.strokeEnd = 0.0 205 | } 206 | 207 | // Paths 208 | unselectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 209 | selectedBoxLayer.path = pathGenerator.pathForBox()?.cgPath 210 | markLayer.path = pathGenerator.pathForMark(state)?.cgPath 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /Sources/Paths/M13CheckboxAddRemovePathGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxAddRemovePathGenerator.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 10/7/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxAddRemovePathGenerator: M13CheckboxPathGenerator { 17 | 18 | //---------------------------- 19 | // MARK: - Box Paths 20 | //---------------------------- 21 | 22 | override func pathForCircle() -> UIBezierPath? { 23 | let radius = (size - boxLineWidth) / 2.0 24 | // Create a circle that starts in the middle left. 25 | return UIBezierPath(arcCenter: CGPoint(x: size / 2.0, y: size / 2.0), 26 | radius: radius, 27 | startAngle: -CGFloat.pi, 28 | endAngle: CGFloat((2 * Double.pi) - Double.pi), 29 | clockwise: true) 30 | } 31 | 32 | override func pathForRoundedRect() -> UIBezierPath? { 33 | let path = UIBezierPath() 34 | let lineOffset: CGFloat = boxLineWidth / 2.0 35 | 36 | let trX: CGFloat = size - lineOffset - cornerRadius 37 | let trY: CGFloat = 0.0 + lineOffset + cornerRadius 38 | let tr = CGPoint(x: trX, y: trY) 39 | 40 | let brX: CGFloat = size - lineOffset - cornerRadius 41 | let brY: CGFloat = size - lineOffset - cornerRadius 42 | let br = CGPoint(x: brX, y: brY) 43 | 44 | let blX: CGFloat = 0.0 + lineOffset + cornerRadius 45 | let blY: CGFloat = size - lineOffset - cornerRadius 46 | let bl = CGPoint(x: blX, y: blY) 47 | 48 | let tlX: CGFloat = 0.0 + lineOffset + cornerRadius 49 | let tlY: CGFloat = 0.0 + lineOffset + cornerRadius 50 | let tl = CGPoint(x: tlX, y: tlY) 51 | 52 | path.move(to: CGPoint(x: ((tl.x + bl.x) / 2.0) - cornerRadius, y: (tl.y + bl.y) / 2.0)) 53 | 54 | // Left side. 55 | let tlXCr: CGFloat = tl.x - cornerRadius 56 | path.addLine(to: CGPoint(x: tlXCr, y: tl.y)) 57 | // Top left arc. 58 | if cornerRadius != 0 { 59 | path.addArc(withCenter: tl, 60 | radius: cornerRadius, 61 | startAngle: CGFloat.pi, 62 | endAngle: CGFloat(Double.pi + (Double.pi / 2)), 63 | clockwise: true) 64 | } 65 | 66 | // Top side. 67 | let trYCr: CGFloat = tr.y - cornerRadius 68 | path.addLine(to: CGPoint(x: tr.x, y: trYCr)) 69 | // Right arc 70 | if cornerRadius != 0 { 71 | path.addArc(withCenter: tr, 72 | radius: cornerRadius, 73 | startAngle: -(CGFloat.pi / 2), 74 | endAngle: 0.0, 75 | clockwise: true) 76 | } 77 | // Right side. 78 | let brXCr: CGFloat = br.x + cornerRadius 79 | path.addLine(to: CGPoint(x: brXCr, y: br.y)) 80 | 81 | // Bottom right arc. 82 | if cornerRadius != 0 { 83 | path.addArc(withCenter: br, 84 | radius: cornerRadius, 85 | startAngle: 0.0, 86 | endAngle: CGFloat.pi / 2, 87 | clockwise: true) 88 | } 89 | // Bottom side. 90 | let blYCr: CGFloat = bl.y + cornerRadius 91 | path.addLine(to: CGPoint(x: bl.x , y: blYCr)) 92 | // Bottom left arc. 93 | if cornerRadius != 0 { 94 | path.addArc(withCenter: bl, 95 | radius: cornerRadius, 96 | startAngle: CGFloat.pi / 2, 97 | endAngle: CGFloat.pi, 98 | clockwise: true) 99 | } 100 | 101 | path.close() 102 | return path 103 | } 104 | 105 | //---------------------------- 106 | // MARK: - Mark Generation 107 | //---------------------------- 108 | 109 | override func pathForMark() -> UIBezierPath? { 110 | let path = UIBezierPath() 111 | 112 | path.move(to: CGPoint(x: size * 0.75, y: size * 0.5)) 113 | path.addLine(to: CGPoint(x: size * 0.25, y: size * 0.5)) 114 | 115 | path.move(to: CGPoint(x: size / 2.0, y: size * 0.5)) 116 | path.addLine(to: CGPoint(x: size / 2.0, y: size * 0.5)) 117 | 118 | return path 119 | } 120 | 121 | override func pathForLongMark() -> UIBezierPath? { 122 | let path = UIBezierPath() 123 | 124 | path.move(to: CGPoint(x: size * 0.75, y: size * 0.5)) 125 | path.addLine(to: CGPoint(x: size * 0.25, y: size * 0.5)) 126 | 127 | path.move(to: CGPoint(x: size / 2.0, y: size * 0.5)) 128 | path.addLine(to: CGPoint(x: size / 2.0, y: size * 0.5)) 129 | 130 | return path 131 | } 132 | 133 | override func pathForMixedMark() -> UIBezierPath? { 134 | return pathForUnselectedMark() 135 | } 136 | 137 | override func pathForLongMixedMark() -> UIBezierPath? { 138 | return pathForLongUnselectedMark() 139 | } 140 | 141 | override func pathForUnselectedMark() -> UIBezierPath? { 142 | let path = UIBezierPath() 143 | 144 | path.move(to: CGPoint(x: size * 0.75, y: size * 0.5)) 145 | path.addLine(to: CGPoint(x: size * 0.25, y: size * 0.5)) 146 | 147 | path.move(to: CGPoint(x: size / 2.0, y: size * 0.25)) 148 | path.addLine(to: CGPoint(x: size / 2.0, y: size * 0.75)) 149 | 150 | return path 151 | } 152 | 153 | override func pathForLongUnselectedMark() -> UIBezierPath? { 154 | let path = UIBezierPath() 155 | 156 | path.move(to: CGPoint(x: size * 0.75, y: size * 0.5)) 157 | path.addLine(to: CGPoint(x: boxLineWidth, y: size * 0.5)) 158 | 159 | path.move(to: CGPoint(x: size / 2.0, y: size * 0.25)) 160 | path.addLine(to: CGPoint(x: size / 2.0, y: size * 0.75)) 161 | 162 | return path 163 | } 164 | } 165 | 166 | -------------------------------------------------------------------------------- /Sources/Paths/M13CheckboxDisclosurePathGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxDisclosurePathGenerator.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 10/10/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxDisclosurePathGenerator: M13CheckboxPathGenerator { 17 | 18 | //---------------------------- 19 | // MARK: - Box Paths 20 | //---------------------------- 21 | 22 | override func pathForCircle() -> UIBezierPath? { 23 | let radius = (size - boxLineWidth) / 2.0 24 | // Create a circle that starts in the middle left. 25 | return UIBezierPath(arcCenter: CGPoint(x: size / 2.0, y: size / 2.0), 26 | radius: radius, 27 | startAngle: -CGFloat.pi, 28 | endAngle: CGFloat((2 * Double.pi) - Double.pi), 29 | clockwise: true) 30 | } 31 | 32 | override func pathForRoundedRect() -> UIBezierPath? { 33 | let path = UIBezierPath() 34 | let lineOffset: CGFloat = boxLineWidth / 2.0 35 | 36 | let trX: CGFloat = size - lineOffset - cornerRadius 37 | let trY: CGFloat = 0.0 + lineOffset + cornerRadius 38 | let tr = CGPoint(x: trX, y: trY) 39 | 40 | let brX: CGFloat = size - lineOffset - cornerRadius 41 | let brY: CGFloat = size - lineOffset - cornerRadius 42 | let br = CGPoint(x: brX, y: brY) 43 | 44 | let blX: CGFloat = 0.0 + lineOffset + cornerRadius 45 | let blY: CGFloat = size - lineOffset - cornerRadius 46 | let bl = CGPoint(x: blX, y: blY) 47 | 48 | let tlX: CGFloat = 0.0 + lineOffset + cornerRadius 49 | let tlY: CGFloat = 0.0 + lineOffset + cornerRadius 50 | let tl = CGPoint(x: tlX, y: tlY) 51 | 52 | path.move(to: CGPoint(x: ((tl.x + bl.x) / 2.0) - cornerRadius, y: (tl.y + bl.y) / 2.0)) 53 | 54 | // Left side. 55 | let tlXCr: CGFloat = tl.x - cornerRadius 56 | path.addLine(to: CGPoint(x: tlXCr, y: tl.y)) 57 | // Top left arc. 58 | if cornerRadius != 0 { 59 | path.addArc(withCenter: tl, 60 | radius: cornerRadius, 61 | startAngle: CGFloat.pi, 62 | endAngle: CGFloat(Double.pi + (Double.pi / 2)), 63 | clockwise: true) 64 | } 65 | 66 | // Top side. 67 | let trYCr: CGFloat = tr.y - cornerRadius 68 | path.addLine(to: CGPoint(x: tr.x, y: trYCr)) 69 | // Right arc 70 | if cornerRadius != 0 { 71 | path.addArc(withCenter: tr, 72 | radius: cornerRadius, 73 | startAngle: -(CGFloat.pi / 2), 74 | endAngle: 0.0, 75 | clockwise: true) 76 | } 77 | // Right side. 78 | let brXCr: CGFloat = br.x + cornerRadius 79 | path.addLine(to: CGPoint(x: brXCr, y: br.y)) 80 | 81 | // Bottom right arc. 82 | if cornerRadius != 0 { 83 | path.addArc(withCenter: br, 84 | radius: cornerRadius, 85 | startAngle: 0.0, 86 | endAngle: CGFloat.pi / 2, 87 | clockwise: true) 88 | } 89 | // Bottom side. 90 | let blYCr: CGFloat = bl.y + cornerRadius 91 | path.addLine(to: CGPoint(x: bl.x , y: blYCr)) 92 | // Bottom left arc. 93 | if cornerRadius != 0 { 94 | path.addArc(withCenter: bl, 95 | radius: cornerRadius, 96 | startAngle: CGFloat.pi / 2, 97 | endAngle: CGFloat.pi, 98 | clockwise: true) 99 | } 100 | 101 | path.close() 102 | return path 103 | } 104 | 105 | //---------------------------- 106 | // MARK: - Mark Generation 107 | //---------------------------- 108 | 109 | override func pathForMark() -> UIBezierPath? { 110 | let path = UIBezierPath() 111 | 112 | path.move(to: CGPoint(x: size * 0.75, y: size * 0.6)) 113 | path.addLine(to: CGPoint(x: size * 0.5, y: size * 0.35)) 114 | path.addLine(to: CGPoint(x: size * 0.25, y: size * 0.6)) 115 | path.addLine(to: CGPoint(x: size * 0.25, y: size * 0.6)) 116 | 117 | return path 118 | } 119 | 120 | override func pathForLongMark() -> UIBezierPath? { 121 | let path = UIBezierPath() 122 | 123 | path.move(to: CGPoint(x: size * 0.75, y: size * 0.6)) 124 | path.addLine(to: CGPoint(x: size * 0.5, y: size * 0.35)) 125 | path.addLine(to: CGPoint(x: size * 0.25, y: size * 0.6)) 126 | path.addLine(to: CGPoint(x: boxLineWidth, y: size * 0.6)) 127 | 128 | return path 129 | } 130 | 131 | override func pathForMixedMark() -> UIBezierPath? { 132 | let path = UIBezierPath() 133 | 134 | path.move(to: CGPoint(x: size * 0.75, y: size * 0.5)) 135 | path.addLine(to: CGPoint(x: size * 0.5, y: size * 0.5)) 136 | path.addLine(to: CGPoint(x: size * 0.25, y: size * 0.5)) 137 | path.addLine(to: CGPoint(x: size * 0.25, y: size * 0.5)) 138 | 139 | return path 140 | } 141 | 142 | override func pathForLongMixedMark() -> UIBezierPath? { 143 | let path = UIBezierPath() 144 | 145 | path.move(to: CGPoint(x: size * 0.75, y: size * 0.5)) 146 | path.addLine(to: CGPoint(x: size * 0.5, y: size * 0.5)) 147 | path.addLine(to: CGPoint(x: size * 0.25, y: size * 0.5)) 148 | path.addLine(to: CGPoint(x: boxLineWidth, y: size * 0.5)) 149 | 150 | return path 151 | } 152 | 153 | override func pathForUnselectedMark() -> UIBezierPath? { 154 | let path = UIBezierPath() 155 | 156 | path.move(to: CGPoint(x: size * 0.75, y: size * 0.4)) 157 | path.addLine(to: CGPoint(x: size * 0.5, y: size * 0.65)) 158 | path.addLine(to: CGPoint(x: size * 0.25, y: size * 0.4)) 159 | path.addLine(to: CGPoint(x: size * 0.25, y: size * 0.4)) 160 | 161 | return path 162 | } 163 | 164 | override func pathForLongUnselectedMark() -> UIBezierPath? { 165 | let path = UIBezierPath() 166 | 167 | path.move(to: CGPoint(x: size * 0.75, y: size * 0.4)) 168 | path.addLine(to: CGPoint(x: size * 0.5, y: size * 0.65)) 169 | path.addLine(to: CGPoint(x: size * 0.25, y: size * 0.4)) 170 | path.addLine(to: CGPoint(x: boxLineWidth, y: size * 0.4)) 171 | 172 | return path 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Sources/Paths/M13CheckboxPathGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxPathGenerator.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 10/6/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | /// The base class that generates the paths for the different mark types. 17 | internal class M13CheckboxPathGenerator { 18 | 19 | //---------------------------- 20 | // MARK: - Properties 21 | //---------------------------- 22 | 23 | /// The maximum width or height the path will be generated with. 24 | var size: CGFloat = 0.0 25 | 26 | /// The line width of the created checkmark. 27 | var checkmarkLineWidth: CGFloat = 1.0 28 | 29 | /// The line width of the created box. 30 | var boxLineWidth: CGFloat = 1.0 31 | 32 | /// The corner radius of the box. 33 | var cornerRadius: CGFloat = 3.0 34 | 35 | /// The box type to create. 36 | var boxType: M13Checkbox.BoxType = DefaultValues.boxType 37 | 38 | //---------------------------- 39 | // MARK: - Box Paths 40 | //---------------------------- 41 | 42 | /** 43 | Creates a path object for the box. 44 | - returns: A `UIBezierPath` representing the box. 45 | */ 46 | final func pathForBox() -> UIBezierPath? { 47 | switch boxType { 48 | case .circle: 49 | return pathForCircle() 50 | case .square: 51 | return pathForRoundedRect() 52 | } 53 | } 54 | 55 | /** 56 | Creates a circular path for the box. The path starts at the top center point of the box. 57 | - returns: A `UIBezierPath` representing the box. 58 | */ 59 | func pathForCircle() -> UIBezierPath? { 60 | let radius = (size - boxLineWidth) / 2.0 61 | // Create a circle that starts in the top right hand corner. 62 | return UIBezierPath(arcCenter: CGPoint(x: size / 2.0, y: size / 2.0), 63 | radius: radius, 64 | startAngle: -(CGFloat.pi / 2), 65 | endAngle: CGFloat((2 * Double.pi) - (Double.pi / 2)), 66 | clockwise: true) 67 | } 68 | 69 | /** 70 | Creates a rounded rect path for the box. The path starts at the top center point of the box. 71 | - returns: A `UIBezierPath` representing the box. 72 | */ 73 | func pathForRoundedRect() -> UIBezierPath? { 74 | let path = UIBezierPath() 75 | let lineOffset: CGFloat = boxLineWidth / 2.0 76 | 77 | let trX: CGFloat = size - lineOffset - cornerRadius 78 | let trY: CGFloat = 0.0 + lineOffset + cornerRadius 79 | let tr = CGPoint(x: trX, y: trY) 80 | 81 | let brX: CGFloat = size - lineOffset - cornerRadius 82 | let brY: CGFloat = size - lineOffset - cornerRadius 83 | let br = CGPoint(x: brX, y: brY) 84 | 85 | let blX: CGFloat = 0.0 + lineOffset + cornerRadius 86 | let blY: CGFloat = size - lineOffset - cornerRadius 87 | let bl = CGPoint(x: blX, y: blY) 88 | 89 | let tlX: CGFloat = 0.0 + lineOffset + cornerRadius 90 | let tlY: CGFloat = 0.0 + lineOffset + cornerRadius 91 | let tl = CGPoint(x: tlX, y: tlY) 92 | 93 | path.move(to: CGPoint(x: (tl.x + tr.x) / 2.0, y: ((tl.y + tr.y) / 2.0) - cornerRadius)) 94 | 95 | // Top side. 96 | let trYCr: CGFloat = tr.y - cornerRadius 97 | path.addLine(to: CGPoint(x: tr.x, y: trYCr)) 98 | 99 | // Right arc 100 | if cornerRadius != 0 { 101 | path.addArc(withCenter: tr, 102 | radius: cornerRadius, 103 | startAngle: -(CGFloat.pi / 2), 104 | endAngle: 0.0, 105 | clockwise: true) 106 | } 107 | // Right side. 108 | let brXCr: CGFloat = br.x + cornerRadius 109 | path.addLine(to: CGPoint(x: brXCr, y: br.y)) 110 | 111 | // Bottom right arc. 112 | if cornerRadius != 0 { 113 | path.addArc(withCenter: br, 114 | radius: cornerRadius, 115 | startAngle: 0.0, 116 | endAngle: CGFloat.pi / 2, 117 | clockwise: true) 118 | } 119 | // Bottom side. 120 | let blYCr: CGFloat = bl.y + cornerRadius 121 | path.addLine(to: CGPoint(x: bl.x , y: blYCr)) 122 | // Bottom left arc. 123 | if cornerRadius != 0 { 124 | path.addArc(withCenter: bl, 125 | radius: cornerRadius, 126 | startAngle: CGFloat.pi / 2, 127 | endAngle: CGFloat.pi, 128 | clockwise: true) 129 | } 130 | 131 | // Left side. 132 | let tlXCr: CGFloat = tl.x - cornerRadius 133 | path.addLine(to: CGPoint(x: tlXCr, y: tl.y)) 134 | // Top left arc. 135 | if cornerRadius != 0 { 136 | path.addArc(withCenter: tl, 137 | radius: cornerRadius, 138 | startAngle: CGFloat.pi, 139 | endAngle: CGFloat(Double.pi + (Double.pi / 2)), 140 | clockwise: true) 141 | } 142 | path.close() 143 | return path 144 | } 145 | 146 | /** 147 | Creates a small dot for the box. 148 | - returns: A `UIBezierPath` representing the dot. 149 | */ 150 | func pathForDot() -> UIBezierPath? { 151 | let boxPath = pathForBox() 152 | let scale: CGFloat = 1.0 / 20.0 153 | boxPath?.apply(CGAffineTransform(scaleX: scale, y: scale)) 154 | boxPath?.apply(CGAffineTransform(translationX: (size - (size * scale)) / 2.0, y: (size - (size * scale)) / 2.0)) 155 | return boxPath 156 | } 157 | 158 | //---------------------------- 159 | // MARK: - Check Generation 160 | //---------------------------- 161 | 162 | /** 163 | Generates the path for the mark for the given state. 164 | - parameter state: The state to generate the mark path for. 165 | - returns: A `UIBezierPath` representing the mark. 166 | */ 167 | final func pathForMark(_ state: M13Checkbox.CheckState?) -> UIBezierPath? { 168 | 169 | guard let state = state else { 170 | return nil 171 | } 172 | 173 | switch state { 174 | case .unchecked: 175 | return pathForUnselectedMark() 176 | case .checked: 177 | return pathForMark() 178 | case .mixed: 179 | return pathForMixedMark() 180 | } 181 | } 182 | 183 | /** 184 | Generates the path for the long mark for the given state used in the spiral animation. 185 | - parameter state: The state to generate the long mark path for. 186 | - returns: A `UIBezierPath` representing the long mark. 187 | */ 188 | final func pathForLongMark(_ state: M13Checkbox.CheckState?) -> UIBezierPath? { 189 | 190 | guard let state = state else { 191 | return nil 192 | } 193 | 194 | switch state { 195 | case .unchecked: 196 | return pathForLongUnselectedMark() 197 | case .checked: 198 | return pathForLongMark() 199 | case .mixed: 200 | return pathForLongMixedMark() 201 | } 202 | } 203 | 204 | /** 205 | Creates a path object for the mark. 206 | - returns: A `UIBezierPath` representing the mark. 207 | */ 208 | func pathForMark() -> UIBezierPath? { 209 | return nil 210 | } 211 | 212 | /** 213 | Creates a path object for the long mark. 214 | - returns: A `UIBezierPath` representing the long mark. 215 | */ 216 | func pathForLongMark() -> UIBezierPath? { 217 | return nil 218 | } 219 | 220 | /** 221 | Creates a path object for the mixed mark. 222 | - returns: A `UIBezierPath` representing the mixed mark. 223 | */ 224 | func pathForMixedMark() -> UIBezierPath? { 225 | return nil 226 | } 227 | 228 | /** 229 | Creates a path object for the long mixed mark. 230 | - returns: A `UIBezierPath` representing the long mixed mark. 231 | */ 232 | func pathForLongMixedMark() -> UIBezierPath? { 233 | return nil 234 | } 235 | 236 | /** 237 | Creates a path object for the unselected mark. 238 | - returns: A `UIBezierPath` representing the unselected mark. 239 | */ 240 | func pathForUnselectedMark() -> UIBezierPath? { 241 | return nil 242 | } 243 | 244 | /** 245 | Creates a path object for the long unselected mark. 246 | - returns: A `UIBezierPath` representing the long unselected mark. 247 | */ 248 | func pathForLongUnselectedMark() -> UIBezierPath? { 249 | return nil 250 | } 251 | 252 | } 253 | -------------------------------------------------------------------------------- /Sources/Paths/M13CheckboxRadioPathGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // M13CheckboxRadioPathGenerator.swift 3 | // M13Checkbox 4 | // 5 | // Created by McQuilkin, Brandon on 10/6/16. 6 | // Copyright © 2016 Brandon McQuilkin. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | import UIKit 15 | 16 | internal class M13CheckboxRadioPathGenerator: M13CheckboxPathGenerator { 17 | 18 | //---------------------------- 19 | // MARK: - Mark Generation 20 | //---------------------------- 21 | 22 | override func pathForMark() -> UIBezierPath? { 23 | let transform = CGAffineTransform(scaleX: 0.665, y: 0.665) 24 | let translate = CGAffineTransform(translationX: size * 0.1675, y: size * 0.1675) 25 | let path = pathForBox() 26 | path?.apply(transform) 27 | path?.apply(translate) 28 | return path 29 | } 30 | 31 | override func pathForLongMark() -> UIBezierPath? { 32 | return pathForBox() 33 | } 34 | 35 | override func pathForMixedMark() -> UIBezierPath? { 36 | return pathForMark() 37 | } 38 | 39 | override func pathForLongMixedMark() -> UIBezierPath? { 40 | return pathForBox() 41 | } 42 | 43 | override func pathForUnselectedMark() -> UIBezierPath? { 44 | return nil 45 | } 46 | 47 | override func pathForLongUnselectedMark() -> UIBezierPath? { 48 | return nil 49 | } 50 | } 51 | 52 | --------------------------------------------------------------------------------