├── .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 | 
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 | 
40 | - **Fill:**
41 |
42 | 
43 | - **Bounce (Stroke):**
44 |
45 | 
46 | - **Bounce (Fill):**
47 |
48 | 
49 | - **Expand (Stroke):**
50 |
51 | 
52 | - **Expand (Fill):**
53 |
54 | 
55 | - **Flat (Stroke):**
56 |
57 | 
58 | - **Flat (Fill):**
59 |
60 | 
61 | - **Spiral:**
62 |
63 | 
64 | - **Fade (Stroke):**
65 |
66 | 
67 | - **Fade (Fill):**
68 |
69 | 
70 | - **Dot (Stroke):**
71 |
72 | 
73 | - **Dot (Fill):**
74 |
75 | 
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 |
--------------------------------------------------------------------------------