├── 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 | --------------------------------------------------------------------------------