├── GestureVisualization
├── Assets.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── TouchVisualizer.swift
├── Info.plist
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── AppDelegate.swift
├── ViewController.swift
└── GestureVisualizer.swift
├── GestureVisualization.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── README.md
└── .gitignore
/GestureVisualization/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/GestureVisualization.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/GestureVisualization.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/GestureVisualization/TouchVisualizer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TouchVisualizer.swift
3 | // GestureVisualization
4 | //
5 | // Created by Shannon Hughes on 5/15/19.
6 | // Copyright © 2019 The Omni Group. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class TouchVisualizer: UIView {
12 |
13 | override func draw(_ rect: CGRect) {
14 | let context = UIGraphicsGetCurrentContext()
15 | context?.saveGState()
16 |
17 | // draw background
18 | context?.setFillColor(UIColor.white.cgColor)
19 | context?.fill(rect)
20 |
21 | // draw touch
22 | context?.setFillColor(UIColor.blue.withAlphaComponent(0.3).cgColor)
23 | context?.fillEllipse(in: self.bounds)
24 |
25 | context?.restoreGState()
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GestureVisualization
2 | A sample app to get visual feedback from UIGestureRecognizer subclasses.
3 |
4 | The view controller is already set up to create a few GestureVisualizers and stick them in the view.
5 |
6 | If you want to observe a custom gesture recognizer, it will be assumed to be a continuous gesture. If that is not the case, you'll have to add that information in isVisualizingDiscreteGesture().
7 |
8 | This project adds some small delays to the visual feedback when messages start to stack up. Otherwise, transitions would happen so quickly that you wouldn't be able to see them.
9 |
10 | To visualize the state transitions, this app does key value observing on the state of the gesture. DO NOT DO THIS in a shipping application! UIGestureRecognizers are meant to be dealt with through their action messages to their targets. Trying to respond to their internal state changes will only bring pain and misery.
11 |
--------------------------------------------------------------------------------
/GestureVisualization/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/GestureVisualization/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 |
--------------------------------------------------------------------------------
/GestureVisualization/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 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | # Pods/
50 | #
51 | # Add this line if you want to avoid checking in source code from the Xcode workspace
52 | # *.xcworkspace
53 |
54 | # Carthage
55 | #
56 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
57 | # Carthage/Checkouts
58 |
59 | Carthage/Build
60 |
61 | # Accio dependency management
62 | Dependencies/
63 | .accio/
64 |
65 | # fastlane
66 | #
67 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
68 | # screenshots whenever they are needed.
69 | # For more information about the recommended setup visit:
70 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
71 |
72 | fastlane/report.xml
73 | fastlane/Preview.html
74 | fastlane/screenshots/**/*.png
75 | fastlane/test_output
76 |
77 | # Code Injection
78 | #
79 | # After new code Injection tools there's a generated folder /iOSInjectionProject
80 | # https://github.com/johnno1962/injectionforxcode
81 |
82 | iOSInjectionProject/
--------------------------------------------------------------------------------
/GestureVisualization/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // GestureVisualization
4 | //
5 | // Created by Shannon Hughes on 4/21/19.
6 | // Copyright © 2019 The Omni Group. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // 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.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // 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.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // 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.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/GestureVisualization/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/GestureVisualization/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // GestureVisualization
4 | //
5 | // Created by Shannon Hughes on 4/21/19.
6 | // Copyright © 2019 The Omni Group. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController, UIGestureRecognizerDelegate {
12 |
13 | @IBOutlet weak var stackView: UIStackView?
14 |
15 | var showTouchOnScreen = true
16 | var noWaitLongpress: UIGestureRecognizer?
17 | var touchVisualizer: TouchVisualizer?
18 |
19 | var gestureVisualizers = [GestureVisualizer]()
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 |
24 | if (showTouchOnScreen) {
25 | let touchGesture = UILongPressGestureRecognizer.init(target: self, action: #selector(touchUpdate(gesture:)))
26 | touchGesture.minimumPressDuration = 0.0
27 | view.addGestureRecognizer(touchGesture)
28 | noWaitLongpress = touchGesture
29 | }
30 |
31 | // let gestures = doubleTapSingleTap(requireToFail: false)
32 | // let gestures = doubleTapSingleTap(requireToFail: true)
33 | let gestures = panLongPressTap()
34 |
35 | for (gestureName, gesture) in gestures {
36 | gesture.delegate = self
37 | view.addGestureRecognizer(gesture)
38 | gesture.addTarget(self, action: #selector(gestureChanged(gesture:)))
39 | let visualizer = GestureVisualizer.init(gesture: gesture, name: gestureName, frame: .zero)
40 | gestureVisualizers.append(visualizer)
41 | gesture.addObserver(self, forKeyPath: "state", options: [.new, .old], context: nil) // DO NOT observe the state of a UIGestureRecognizer in a real app. See README.md
42 | stackView?.addArrangedSubview(visualizer)
43 | }
44 | }
45 |
46 | //MARK: UIGestureRecognizerDelegate
47 |
48 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
49 |
50 | if gestureRecognizer == noWaitLongpress || otherGestureRecognizer == noWaitLongpress {
51 | return true // showing touches depends on an extra gesture recognizer that isn't visualized, so gestures have to be allowed to recognize with it
52 | }
53 |
54 | // default is false
55 | return false
56 | // return true
57 | }
58 |
59 | //MARK: Gesture Set Ups
60 |
61 | func doubleTapSingleTap(requireToFail: Bool) -> ([(String, UIGestureRecognizer)]) {
62 | let doubleTap = UITapGestureRecognizer();
63 | doubleTap.numberOfTapsRequired = 2;
64 | let singleTap = UITapGestureRecognizer();
65 | if requireToFail {
66 | singleTap.require(toFail: doubleTap)
67 | }
68 |
69 | return [("Tap", singleTap),
70 | ("Double Tap", doubleTap)]
71 | }
72 |
73 | func panLongPressTap() -> ([(String, UIGestureRecognizer)]) {
74 | return [("Longpress", UILongPressGestureRecognizer()),
75 | ("Pan", UIPanGestureRecognizer()),
76 | // ("Tap", UITapGestureRecognizer())
77 | ]
78 | }
79 |
80 | //MARK: Update Gesture Diagrams
81 |
82 | @objc func gestureChanged(gesture: UIGestureRecognizer) {
83 | for visualizer in gestureVisualizers {
84 | if (gesture == visualizer.gesture) {
85 | visualizer.animateMessageSend()
86 | }
87 | }
88 | Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (_) in
89 | for visualizer in self.gestureVisualizers {
90 | visualizer.setNeedsDisplay() // clears out state of gestures that were waiting for this one to fail
91 | }
92 | }
93 | }
94 |
95 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
96 | // DO NOT observe the state of a UIGestureRecognizer in a real app. See README.md
97 | guard let gesture = object as? UIGestureRecognizer else { return }
98 | guard self.view.gestureRecognizers?.contains(gesture) ?? false else { return }
99 | guard keyPath == "state" else { return }
100 |
101 | for visualizer in gestureVisualizers {
102 | if visualizer.gesture == gesture {
103 | guard let oldStateRaw = change?[NSKeyValueChangeKey.oldKey] as? Int, let newStateRaw = change?[NSKeyValueChangeKey.newKey] as? Int else { return }
104 | guard let oldState = UIGestureRecognizer.State(rawValue: oldStateRaw), let newState = UIGestureRecognizer.State(rawValue: newStateRaw) else { return }
105 | visualizer.animate((oldState, newState))
106 | return
107 | }
108 | }
109 | }
110 |
111 | //MARK: Visualize Initial Touch
112 |
113 | let minRadius: CGFloat = 3
114 | let radiusGrowthDelta: CGFloat = 25
115 | let animationTime = 0.2
116 |
117 | @objc func touchUpdate(gesture: UIGestureRecognizer) {
118 | if gesture.state == .ended || gesture.state == .failed || gesture.state == .cancelled {
119 | guard let touchView = touchVisualizer else { return }
120 | removeTouchView(touchView: touchView)
121 | } else {
122 | // update the location
123 | let location = gesture.location(in: self.view)
124 | if let touchView = touchVisualizer {
125 | // we have a touch view. update its location.
126 | var newFrame = touchView.frame
127 | newFrame.origin.x = location.x - touchView.frame.size.width/2.0
128 | newFrame.origin.y = location.y - touchView.frame.size.height/2.0
129 | touchView.frame = newFrame
130 | } else {
131 | // we have no touch view. create one.
132 | let frame = CGRect(x: location.x - minRadius, y: location.y - minRadius, width: minRadius * 2, height: minRadius * 2)
133 | let touchVis = TouchVisualizer.init(frame: frame)
134 | self.view.addSubview(touchVis)
135 | UIView.animate(withDuration: animationTime) {
136 | touchVis.frame = touchVis.frame.inset(by: UIEdgeInsets.init(top: -self.radiusGrowthDelta, left: -self.radiusGrowthDelta, bottom: -self.radiusGrowthDelta, right: -self.radiusGrowthDelta))
137 | }
138 | touchVisualizer = touchVis
139 | }
140 | }
141 | }
142 |
143 | func removeTouchView(touchView: UIView) {
144 | if (touchView == self.touchVisualizer) {
145 | self.touchVisualizer = nil
146 | }
147 | let delay = touchView.frame.size.width > radiusGrowthDelta ? 0 : animationTime
148 | DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
149 | UIView.animate(withDuration: self.animationTime, animations: {
150 | touchView.frame = touchView.frame.inset(by: UIEdgeInsets.init(top: self.radiusGrowthDelta, left: self.radiusGrowthDelta, bottom: self.radiusGrowthDelta, right: self.radiusGrowthDelta))
151 | }) { (_) in
152 | touchView.removeFromSuperview()
153 | }
154 | }
155 | }
156 |
157 | }
158 |
159 |
--------------------------------------------------------------------------------
/GestureVisualization/GestureVisualizer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GestureVisualiser.swift
3 | // GestureVisualization
4 | //
5 | // Created by Shannon Hughes on 4/21/19.
6 | // Copyright © 2019 The Omni Group. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class GestureVisualizer: UIView {
12 |
13 | public let gesture: UIGestureRecognizer
14 | let _name: NSAttributedString
15 | let _delay = 0.5
16 |
17 | var _drawAlternateChanged = false
18 | var _sentMessage = false
19 |
20 | var transitionsToAnimate: [(UIGestureRecognizer.State, UIGestureRecognizer.State)]?
21 | var timer: Timer?
22 |
23 | init(gesture: UIGestureRecognizer, name: String, frame: CGRect) {
24 | self.gesture = gesture
25 | _name = NSAttributedString (string: name, attributes: [ NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 20)])
26 | super.init(frame: frame)
27 | }
28 |
29 | required init?(coder aDecoder: NSCoder) {
30 | fatalError("init(coder:) has not been implemented")
31 | }
32 |
33 | override var intrinsicContentSize: CGSize {
34 | if isVisualizingDiscreteGesture() {
35 | return CGSize(width: size(circleCount: 2), height: size(circleCount: 2))
36 | } else {
37 | return CGSize(width: size(circleCount: 3), height: size(circleCount: 4))
38 | }
39 | }
40 |
41 | public func animate(_ transition: (UIGestureRecognizer.State, UIGestureRecognizer.State)) {
42 | if (transitionsToAnimate == nil) {
43 | transitionsToAnimate = [transition]
44 | } else {
45 | transitionsToAnimate?.append(transition)
46 | }
47 | setNeedsDisplay()
48 | if timer == nil {
49 | timer = Timer.scheduledTimer(withTimeInterval: _delay, repeats: true) { (_) in
50 | self.transitionsToAnimate?.removeFirst()
51 | self.setNeedsDisplay()
52 | guard let remainingTransitions = self.transitionsToAnimate else { return }
53 | if remainingTransitions.isEmpty {
54 | self.timer?.invalidate()
55 | self.timer = nil
56 | }
57 | }
58 | }
59 | }
60 |
61 | public func animateMessageSend() {
62 | _sentMessage = true
63 | setNeedsDisplay()
64 | Timer.scheduledTimer(withTimeInterval: _delay * 2, repeats: true) { (timer) in
65 | if (self.gesture.state != .changed) {
66 | self._sentMessage = false
67 | self.setNeedsDisplay()
68 | timer.invalidate()
69 | }
70 | }
71 | }
72 |
73 | //MARK: State Machine Basics
74 |
75 | func possibleTransitions() -> [(UIGestureRecognizer.State, UIGestureRecognizer.State)] {
76 | if isVisualizingDiscreteGesture() {
77 | return [(.possible, .failed), (.possible, .recognized)]
78 | } else {
79 | return [(.possible, .failed), (.possible, .began),
80 | (.began, .changed), (.began, .failed), (.began, .cancelled),
81 | (.changed, .changed), (.changed, .failed), (.changed, .cancelled),
82 | (.changed, .recognized)]
83 | }
84 | }
85 |
86 | func states() -> [UIGestureRecognizer.State] {
87 | if isVisualizingDiscreteGesture() {
88 | return [.possible, .failed, .recognized]
89 | } else {
90 | return [.possible, .began, .changed, .recognized, .failed, .cancelled];
91 | }
92 | }
93 |
94 | func isVisualizingDiscreteGesture() -> Bool
95 | {
96 | return gesture is UITapGestureRecognizer || gesture is UISwipeGestureRecognizer
97 | }
98 |
99 | //MARK: Drawing
100 |
101 | override func draw(_ rect: CGRect) {
102 |
103 | self._drawAlternateChanged = !self._drawAlternateChanged
104 |
105 | let context = UIGraphicsGetCurrentContext()
106 | context?.saveGState()
107 |
108 | // draw background
109 | context?.setFillColor(UIColor.white.cgColor)
110 | context?.fill(rect)
111 | if (_sentMessage) {
112 | context?.setFillColor(UIColor.blue.withAlphaComponent(0.25).cgColor)
113 | context?.fill(rect)
114 | }
115 |
116 | // draw titles
117 | _name.draw(at: CGPoint(x: 10, y: 5))
118 |
119 | // draw lines
120 | context?.setStrokeColor(UIColor.black.cgColor)
121 | let currentTransition = transitionsToAnimate?.first
122 | for (source, destination) in possibleTransitions() {
123 | if let (curSource, curDestination) = currentTransition, source == curSource && destination == curDestination {
124 | context?.setLineWidth(4)
125 | } else {
126 | context?.setLineWidth(1)
127 | }
128 | let sourcePoint = location(for: source)
129 | let destinationPoint = location(for: destination)
130 | let controlPoint = niceControlPoint(start: sourcePoint, end: destinationPoint)
131 | context?.move(to: sourcePoint)
132 | context?.addQuadCurve(to: destinationPoint, control: controlPoint)
133 | context?.strokePath()
134 | }
135 |
136 | // draw states
137 | context?.setLineWidth(1)
138 | for state in states()
139 | {
140 | let activeState = currentTransition?.1 ?? gesture.state
141 | let stateCenter = location(for: state)
142 | func addCirclePath() {
143 | context?.addArc(center: stateCenter, radius: radius, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
144 | }
145 | let drawLight = state != activeState || (!_drawAlternateChanged && state == .changed)
146 | let alpha: CGFloat = drawLight ? 0.25 : 1.0
147 | let baseColor = color(for: state)
148 | addCirclePath()
149 | if (alpha < 1) {
150 | context?.setFillColor(UIColor.white.cgColor)
151 | context?.fillPath()
152 | }
153 | addCirclePath()
154 | context?.setFillColor(baseColor.withAlphaComponent(alpha).cgColor)
155 | context?.fillPath()
156 | addCirclePath()
157 | context?.setStrokeColor(baseColor.cgColor)
158 | context?.strokePath()
159 |
160 | let stateLabel = label(for: state, active: activeState == state)
161 | let labelSize = stateLabel.size()
162 | let drawingPoint = CGPoint(x: stateCenter.x - labelSize.width/2.0, y: stateCenter.y - labelSize.height/2.0)
163 | stateLabel.draw(at: drawingPoint)
164 | }
165 |
166 | context?.restoreGState()
167 |
168 | if (gesture.state == .failed || gesture.state == .cancelled) {
169 | Timer.scheduledTimer(withTimeInterval: _delay, repeats: false) { (_) in
170 | self.setNeedsDisplay()
171 | }
172 | }
173 | }
174 |
175 | //MARK: Helpers
176 |
177 | func niceControlPoint(start: CGPoint, end: CGPoint) -> CGPoint {
178 | let deltaX = end.x - start.x
179 | let deltaY = end.y - start.y
180 | let slope: CGFloat = deltaX/deltaY > 0 ? 1 : -1
181 | let midPoint = CGPoint(x: start.x + deltaX/2, y: start.y + deltaY/2)
182 | var transform = CGAffineTransform.identity.translatedBy(x: midPoint.x, y: midPoint.y)
183 | transform = transform.rotated(by: slope * CGFloat.pi/2.0)
184 | transform = transform.translatedBy(x: -midPoint.x, y: -midPoint.y)
185 | return start.applying(transform)
186 | }
187 |
188 | func color(for state: UIGestureRecognizer.State) -> UIColor {
189 | var baseColor: UIColor
190 | switch state {
191 | case .possible, .began, .changed:
192 | baseColor = .blue
193 | case .failed, .cancelled:
194 | baseColor = .red
195 | case .ended:
196 | baseColor = .green
197 |
198 | @unknown default:
199 | baseColor = .black
200 | }
201 | return baseColor
202 | }
203 |
204 | let radius = CGFloat(40)
205 | let padding = CGFloat(30)
206 |
207 | func size(circleCount: CGFloat) -> CGFloat {
208 | return (2*padding/*edgepadding*/ + circleCount*2*radius/*circles*/ + (circleCount - 1)*radius/*space between circles*/)
209 | }
210 |
211 | func location(for state: UIGestureRecognizer.State) -> CGPoint {
212 | let oneCircleOffsetFromEdge = padding + radius
213 | let oneCircleOffsetFromAnother = 3 * radius
214 | if isVisualizingDiscreteGesture() {
215 | switch state {
216 | case .possible:
217 | return CGPoint(x: size(circleCount: 2)/2.0, y: oneCircleOffsetFromEdge)
218 |
219 | case .failed:
220 | return CGPoint(x: padding + radius, y: size(circleCount: 2) - oneCircleOffsetFromEdge)
221 | case .ended:
222 | return CGPoint(x: size(circleCount: 2) - oneCircleOffsetFromEdge, y: size(circleCount: 2) - oneCircleOffsetFromEdge)
223 |
224 | default:
225 | return .zero
226 | }
227 | } else {
228 | switch state {
229 | case .possible:
230 | return CGPoint(x: oneCircleOffsetFromEdge + radius, y: oneCircleOffsetFromEdge)
231 |
232 | case .failed:
233 | return CGPoint(x: oneCircleOffsetFromEdge, y: oneCircleOffsetFromEdge + oneCircleOffsetFromAnother)
234 | case .began:
235 | return CGPoint(x: oneCircleOffsetFromEdge + oneCircleOffsetFromAnother, y: oneCircleOffsetFromEdge + oneCircleOffsetFromAnother)
236 | case .cancelled:
237 | return CGPoint(x: oneCircleOffsetFromEdge + 2*oneCircleOffsetFromAnother, y: oneCircleOffsetFromEdge + oneCircleOffsetFromAnother)
238 |
239 | case .changed:
240 | return CGPoint(x: oneCircleOffsetFromEdge + 1.25*oneCircleOffsetFromAnother, y: oneCircleOffsetFromEdge + 2*oneCircleOffsetFromAnother)
241 |
242 | case .ended:
243 | return CGPoint(x: oneCircleOffsetFromEdge + 1.5*oneCircleOffsetFromAnother, y: oneCircleOffsetFromEdge + 3*oneCircleOffsetFromAnother)
244 |
245 | default:
246 | return .zero
247 | }
248 | }
249 | }
250 |
251 | func label(for state: UIGestureRecognizer.State, active: Bool) -> NSAttributedString {
252 | let title: String
253 | switch state {
254 | case .possible:
255 | title = "possible"
256 | case .began:
257 | title = "began"
258 | case .changed:
259 | title = "changed"
260 | case .ended:
261 | title = "ended"
262 | case .failed:
263 | title = "failed"
264 | case .cancelled:
265 | title = "cancelled"
266 | default:
267 | title = "unknown"
268 | }
269 | let color = active ? UIColor.white : UIColor.black
270 | return NSAttributedString.init(string: title, attributes: [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 12), NSAttributedString.Key.foregroundColor : color])
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/GestureVisualization.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | FB26D617228CAAA000F6E5A2 /* TouchVisualizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB26D616228CAAA000F6E5A2 /* TouchVisualizer.swift */; };
11 | FB6E5B9F226CAC4C00D3D348 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB6E5B9E226CAC4C00D3D348 /* AppDelegate.swift */; };
12 | FB6E5BA1226CAC4C00D3D348 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB6E5BA0226CAC4C00D3D348 /* ViewController.swift */; };
13 | FB6E5BA4226CAC4C00D3D348 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FB6E5BA2226CAC4C00D3D348 /* Main.storyboard */; };
14 | FB6E5BA6226CAC4D00D3D348 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FB6E5BA5226CAC4D00D3D348 /* Assets.xcassets */; };
15 | FB6E5BA9226CAC4E00D3D348 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FB6E5BA7226CAC4E00D3D348 /* LaunchScreen.storyboard */; };
16 | FB6E5BB1226CACB600D3D348 /* GestureVisualizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB6E5BB0226CACB600D3D348 /* GestureVisualizer.swift */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXFileReference section */
20 | FB26D616228CAAA000F6E5A2 /* TouchVisualizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchVisualizer.swift; sourceTree = ""; };
21 | FB6E5B9B226CAC4C00D3D348 /* GestureVisualization.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GestureVisualization.app; sourceTree = BUILT_PRODUCTS_DIR; };
22 | FB6E5B9E226CAC4C00D3D348 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
23 | FB6E5BA0226CAC4C00D3D348 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
24 | FB6E5BA3226CAC4C00D3D348 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
25 | FB6E5BA5226CAC4D00D3D348 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
26 | FB6E5BA8226CAC4E00D3D348 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
27 | FB6E5BAA226CAC4E00D3D348 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
28 | FB6E5BB0226CACB600D3D348 /* GestureVisualizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureVisualizer.swift; sourceTree = ""; };
29 | /* End PBXFileReference section */
30 |
31 | /* Begin PBXFrameworksBuildPhase section */
32 | FB6E5B98226CAC4C00D3D348 /* Frameworks */ = {
33 | isa = PBXFrameworksBuildPhase;
34 | buildActionMask = 2147483647;
35 | files = (
36 | );
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | /* End PBXFrameworksBuildPhase section */
40 |
41 | /* Begin PBXGroup section */
42 | FB6E5B92226CAC4C00D3D348 = {
43 | isa = PBXGroup;
44 | children = (
45 | FB6E5B9D226CAC4C00D3D348 /* GestureVisualization */,
46 | FB6E5B9C226CAC4C00D3D348 /* Products */,
47 | );
48 | sourceTree = "";
49 | };
50 | FB6E5B9C226CAC4C00D3D348 /* Products */ = {
51 | isa = PBXGroup;
52 | children = (
53 | FB6E5B9B226CAC4C00D3D348 /* GestureVisualization.app */,
54 | );
55 | name = Products;
56 | sourceTree = "";
57 | };
58 | FB6E5B9D226CAC4C00D3D348 /* GestureVisualization */ = {
59 | isa = PBXGroup;
60 | children = (
61 | FB6E5B9E226CAC4C00D3D348 /* AppDelegate.swift */,
62 | FB6E5BA0226CAC4C00D3D348 /* ViewController.swift */,
63 | FB6E5BA2226CAC4C00D3D348 /* Main.storyboard */,
64 | FB6E5BA5226CAC4D00D3D348 /* Assets.xcassets */,
65 | FB6E5BA7226CAC4E00D3D348 /* LaunchScreen.storyboard */,
66 | FB6E5BAA226CAC4E00D3D348 /* Info.plist */,
67 | FB6E5BB0226CACB600D3D348 /* GestureVisualizer.swift */,
68 | FB26D616228CAAA000F6E5A2 /* TouchVisualizer.swift */,
69 | );
70 | path = GestureVisualization;
71 | sourceTree = "";
72 | };
73 | /* End PBXGroup section */
74 |
75 | /* Begin PBXNativeTarget section */
76 | FB6E5B9A226CAC4C00D3D348 /* GestureVisualization */ = {
77 | isa = PBXNativeTarget;
78 | buildConfigurationList = FB6E5BAD226CAC4E00D3D348 /* Build configuration list for PBXNativeTarget "GestureVisualization" */;
79 | buildPhases = (
80 | FB6E5B97226CAC4C00D3D348 /* Sources */,
81 | FB6E5B98226CAC4C00D3D348 /* Frameworks */,
82 | FB6E5B99226CAC4C00D3D348 /* Resources */,
83 | );
84 | buildRules = (
85 | );
86 | dependencies = (
87 | );
88 | name = GestureVisualization;
89 | productName = GestureVisualization;
90 | productReference = FB6E5B9B226CAC4C00D3D348 /* GestureVisualization.app */;
91 | productType = "com.apple.product-type.application";
92 | };
93 | /* End PBXNativeTarget section */
94 |
95 | /* Begin PBXProject section */
96 | FB6E5B93226CAC4C00D3D348 /* Project object */ = {
97 | isa = PBXProject;
98 | attributes = {
99 | LastSwiftUpdateCheck = 1020;
100 | LastUpgradeCheck = 1020;
101 | ORGANIZATIONNAME = "The Omni Group";
102 | TargetAttributes = {
103 | FB6E5B9A226CAC4C00D3D348 = {
104 | CreatedOnToolsVersion = 10.2.1;
105 | };
106 | };
107 | };
108 | buildConfigurationList = FB6E5B96226CAC4C00D3D348 /* Build configuration list for PBXProject "GestureVisualization" */;
109 | compatibilityVersion = "Xcode 9.3";
110 | developmentRegion = en;
111 | hasScannedForEncodings = 0;
112 | knownRegions = (
113 | en,
114 | Base,
115 | );
116 | mainGroup = FB6E5B92226CAC4C00D3D348;
117 | productRefGroup = FB6E5B9C226CAC4C00D3D348 /* Products */;
118 | projectDirPath = "";
119 | projectRoot = "";
120 | targets = (
121 | FB6E5B9A226CAC4C00D3D348 /* GestureVisualization */,
122 | );
123 | };
124 | /* End PBXProject section */
125 |
126 | /* Begin PBXResourcesBuildPhase section */
127 | FB6E5B99226CAC4C00D3D348 /* Resources */ = {
128 | isa = PBXResourcesBuildPhase;
129 | buildActionMask = 2147483647;
130 | files = (
131 | FB6E5BA9226CAC4E00D3D348 /* LaunchScreen.storyboard in Resources */,
132 | FB6E5BA6226CAC4D00D3D348 /* Assets.xcassets in Resources */,
133 | FB6E5BA4226CAC4C00D3D348 /* Main.storyboard in Resources */,
134 | );
135 | runOnlyForDeploymentPostprocessing = 0;
136 | };
137 | /* End PBXResourcesBuildPhase section */
138 |
139 | /* Begin PBXSourcesBuildPhase section */
140 | FB6E5B97226CAC4C00D3D348 /* Sources */ = {
141 | isa = PBXSourcesBuildPhase;
142 | buildActionMask = 2147483647;
143 | files = (
144 | FB6E5BA1226CAC4C00D3D348 /* ViewController.swift in Sources */,
145 | FB26D617228CAAA000F6E5A2 /* TouchVisualizer.swift in Sources */,
146 | FB6E5B9F226CAC4C00D3D348 /* AppDelegate.swift in Sources */,
147 | FB6E5BB1226CACB600D3D348 /* GestureVisualizer.swift in Sources */,
148 | );
149 | runOnlyForDeploymentPostprocessing = 0;
150 | };
151 | /* End PBXSourcesBuildPhase section */
152 |
153 | /* Begin PBXVariantGroup section */
154 | FB6E5BA2226CAC4C00D3D348 /* Main.storyboard */ = {
155 | isa = PBXVariantGroup;
156 | children = (
157 | FB6E5BA3226CAC4C00D3D348 /* Base */,
158 | );
159 | name = Main.storyboard;
160 | sourceTree = "";
161 | };
162 | FB6E5BA7226CAC4E00D3D348 /* LaunchScreen.storyboard */ = {
163 | isa = PBXVariantGroup;
164 | children = (
165 | FB6E5BA8226CAC4E00D3D348 /* Base */,
166 | );
167 | name = LaunchScreen.storyboard;
168 | sourceTree = "";
169 | };
170 | /* End PBXVariantGroup section */
171 |
172 | /* Begin XCBuildConfiguration section */
173 | FB6E5BAB226CAC4E00D3D348 /* Debug */ = {
174 | isa = XCBuildConfiguration;
175 | buildSettings = {
176 | ALWAYS_SEARCH_USER_PATHS = NO;
177 | CLANG_ANALYZER_NONNULL = YES;
178 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
179 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
180 | CLANG_CXX_LIBRARY = "libc++";
181 | CLANG_ENABLE_MODULES = YES;
182 | CLANG_ENABLE_OBJC_ARC = YES;
183 | CLANG_ENABLE_OBJC_WEAK = YES;
184 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
185 | CLANG_WARN_BOOL_CONVERSION = YES;
186 | CLANG_WARN_COMMA = YES;
187 | CLANG_WARN_CONSTANT_CONVERSION = YES;
188 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
189 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
190 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
191 | CLANG_WARN_EMPTY_BODY = YES;
192 | CLANG_WARN_ENUM_CONVERSION = YES;
193 | CLANG_WARN_INFINITE_RECURSION = YES;
194 | CLANG_WARN_INT_CONVERSION = YES;
195 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
196 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
197 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
198 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
199 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
200 | CLANG_WARN_STRICT_PROTOTYPES = YES;
201 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
202 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
203 | CLANG_WARN_UNREACHABLE_CODE = YES;
204 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
205 | CODE_SIGN_IDENTITY = "iPhone Developer";
206 | COPY_PHASE_STRIP = NO;
207 | DEBUG_INFORMATION_FORMAT = dwarf;
208 | ENABLE_STRICT_OBJC_MSGSEND = YES;
209 | ENABLE_TESTABILITY = YES;
210 | GCC_C_LANGUAGE_STANDARD = gnu11;
211 | GCC_DYNAMIC_NO_PIC = NO;
212 | GCC_NO_COMMON_BLOCKS = YES;
213 | GCC_OPTIMIZATION_LEVEL = 0;
214 | GCC_PREPROCESSOR_DEFINITIONS = (
215 | "DEBUG=1",
216 | "$(inherited)",
217 | );
218 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
219 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
220 | GCC_WARN_UNDECLARED_SELECTOR = YES;
221 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
222 | GCC_WARN_UNUSED_FUNCTION = YES;
223 | GCC_WARN_UNUSED_VARIABLE = YES;
224 | IPHONEOS_DEPLOYMENT_TARGET = 12.2;
225 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
226 | MTL_FAST_MATH = YES;
227 | ONLY_ACTIVE_ARCH = YES;
228 | SDKROOT = iphoneos;
229 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
230 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
231 | };
232 | name = Debug;
233 | };
234 | FB6E5BAC226CAC4E00D3D348 /* Release */ = {
235 | isa = XCBuildConfiguration;
236 | buildSettings = {
237 | ALWAYS_SEARCH_USER_PATHS = NO;
238 | CLANG_ANALYZER_NONNULL = YES;
239 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
240 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
241 | CLANG_CXX_LIBRARY = "libc++";
242 | CLANG_ENABLE_MODULES = YES;
243 | CLANG_ENABLE_OBJC_ARC = YES;
244 | CLANG_ENABLE_OBJC_WEAK = YES;
245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
246 | CLANG_WARN_BOOL_CONVERSION = YES;
247 | CLANG_WARN_COMMA = YES;
248 | CLANG_WARN_CONSTANT_CONVERSION = YES;
249 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
251 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
252 | CLANG_WARN_EMPTY_BODY = YES;
253 | CLANG_WARN_ENUM_CONVERSION = YES;
254 | CLANG_WARN_INFINITE_RECURSION = YES;
255 | CLANG_WARN_INT_CONVERSION = YES;
256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
257 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
258 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
259 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
260 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
261 | CLANG_WARN_STRICT_PROTOTYPES = YES;
262 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
263 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
264 | CLANG_WARN_UNREACHABLE_CODE = YES;
265 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
266 | CODE_SIGN_IDENTITY = "iPhone Developer";
267 | COPY_PHASE_STRIP = NO;
268 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
269 | ENABLE_NS_ASSERTIONS = NO;
270 | ENABLE_STRICT_OBJC_MSGSEND = YES;
271 | GCC_C_LANGUAGE_STANDARD = gnu11;
272 | GCC_NO_COMMON_BLOCKS = YES;
273 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
274 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
275 | GCC_WARN_UNDECLARED_SELECTOR = YES;
276 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
277 | GCC_WARN_UNUSED_FUNCTION = YES;
278 | GCC_WARN_UNUSED_VARIABLE = YES;
279 | IPHONEOS_DEPLOYMENT_TARGET = 12.2;
280 | MTL_ENABLE_DEBUG_INFO = NO;
281 | MTL_FAST_MATH = YES;
282 | SDKROOT = iphoneos;
283 | SWIFT_COMPILATION_MODE = wholemodule;
284 | SWIFT_OPTIMIZATION_LEVEL = "-O";
285 | VALIDATE_PRODUCT = YES;
286 | };
287 | name = Release;
288 | };
289 | FB6E5BAE226CAC4E00D3D348 /* Debug */ = {
290 | isa = XCBuildConfiguration;
291 | buildSettings = {
292 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
293 | CODE_SIGN_STYLE = Automatic;
294 | DEVELOPMENT_TEAM = 9498W2E23C;
295 | INFOPLIST_FILE = GestureVisualization/Info.plist;
296 | LD_RUNPATH_SEARCH_PATHS = (
297 | "$(inherited)",
298 | "@executable_path/Frameworks",
299 | );
300 | PRODUCT_BUNDLE_IDENTIFIER = com.omnigroup.GestureVisualization;
301 | PRODUCT_NAME = "$(TARGET_NAME)";
302 | SWIFT_VERSION = 5.0;
303 | TARGETED_DEVICE_FAMILY = "1,2";
304 | };
305 | name = Debug;
306 | };
307 | FB6E5BAF226CAC4E00D3D348 /* Release */ = {
308 | isa = XCBuildConfiguration;
309 | buildSettings = {
310 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
311 | CODE_SIGN_STYLE = Automatic;
312 | DEVELOPMENT_TEAM = 9498W2E23C;
313 | INFOPLIST_FILE = GestureVisualization/Info.plist;
314 | LD_RUNPATH_SEARCH_PATHS = (
315 | "$(inherited)",
316 | "@executable_path/Frameworks",
317 | );
318 | PRODUCT_BUNDLE_IDENTIFIER = com.omnigroup.GestureVisualization;
319 | PRODUCT_NAME = "$(TARGET_NAME)";
320 | SWIFT_VERSION = 5.0;
321 | TARGETED_DEVICE_FAMILY = "1,2";
322 | };
323 | name = Release;
324 | };
325 | /* End XCBuildConfiguration section */
326 |
327 | /* Begin XCConfigurationList section */
328 | FB6E5B96226CAC4C00D3D348 /* Build configuration list for PBXProject "GestureVisualization" */ = {
329 | isa = XCConfigurationList;
330 | buildConfigurations = (
331 | FB6E5BAB226CAC4E00D3D348 /* Debug */,
332 | FB6E5BAC226CAC4E00D3D348 /* Release */,
333 | );
334 | defaultConfigurationIsVisible = 0;
335 | defaultConfigurationName = Release;
336 | };
337 | FB6E5BAD226CAC4E00D3D348 /* Build configuration list for PBXNativeTarget "GestureVisualization" */ = {
338 | isa = XCConfigurationList;
339 | buildConfigurations = (
340 | FB6E5BAE226CAC4E00D3D348 /* Debug */,
341 | FB6E5BAF226CAC4E00D3D348 /* Release */,
342 | );
343 | defaultConfigurationIsVisible = 0;
344 | defaultConfigurationName = Release;
345 | };
346 | /* End XCConfigurationList section */
347 | };
348 | rootObject = FB6E5B93226CAC4C00D3D348 /* Project object */;
349 | }
350 |
--------------------------------------------------------------------------------