├── AKSynthOneKnob.pcvd ├── Knob+Touches.swift ├── KnobStyleKit.swift ├── KnobView.swift ├── README.md ├── SynthUISpike.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── wu.xcuserdatad │ └── xcschemes │ ├── SynthUISpike.xcscheme │ └── xcschememanagement.plist └── SynthUISpike ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist └── ViewController.swift /AKSynthOneKnob.pcvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogcode/SynthKnobExample/c6a0e67e2357ee5d30578d18c5615779d2c845dc/AKSynthOneKnob.pcvd -------------------------------------------------------------------------------- /Knob+Touches.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Knob+Touches.swift 3 | // Swift Synth 4 | // 5 | // Created by Matthew Fecher on 1/9/16. 6 | // Copyright © 2016 AudioKit. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension KnobView { 12 | 13 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 14 | for touch in touches { 15 | let touchPoint = touch.location(in: self) 16 | lastX = touchPoint.x 17 | lastY = touchPoint.y 18 | } 19 | } 20 | 21 | override func touchesMoved(_ touches: Set, with event: UIEvent?) { 22 | for touch in touches { 23 | let touchPoint = touch.location(in: self) 24 | setPercentagesWithTouchPoint(touchPoint) 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /KnobStyleKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KnobStyleKit.swift 3 | // UISpike 4 | // 5 | // Created by Matthew Fecher on 7/21/17. 6 | // Copyright © 2017 AudioKit. All rights reserved. 7 | // 8 | // Generated by PaintCode 9 | // http://www.paintcodeapp.com 10 | // 11 | // This code was generated by Trial version of PaintCode, therefore cannot be used for commercial purposes. 12 | // 13 | 14 | 15 | 16 | import UIKit 17 | 18 | public class KnobStyleKit : NSObject { 19 | 20 | //// Drawing Methods 21 | 22 | @objc dynamic public class func drawKnobOne(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 124, height: 124), resizing: ResizingBehavior = .aspectFit, knobValue: CGFloat = 0.5) { 23 | //// General Declarations 24 | let context = UIGraphicsGetCurrentContext()! 25 | 26 | //// Resize to Target Frame 27 | context.saveGState() 28 | let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 124, height: 124), target: targetFrame) 29 | context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) 30 | context.scaleBy(x: resizedFrame.width / 124, y: resizedFrame.height / 124) 31 | let resizedShadowScale: CGFloat = min(resizedFrame.width / 124, resizedFrame.height / 124) 32 | 33 | 34 | //// Color Declarations 35 | let black = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000) 36 | let shadowColor = UIColor(red: 1.000, green: 0.596, blue: 0.000, alpha: 1.000) 37 | let knobBottom = UIColor(red: 0.180, green: 0.180, blue: 0.192, alpha: 1.000) 38 | let knobLight = UIColor(red: 0.498, green: 0.498, blue: 0.510, alpha: 1.000) 39 | let knobTop2 = UIColor(red: 0.141, green: 0.141, blue: 0.161, alpha: 1.000) 40 | let orange2 = UIColor(red: 0.902, green: 0.533, blue: 0.008, alpha: 1.000) 41 | 42 | //// Gradient Declarations 43 | let edge2 = CGGradient(colorsSpace: nil, colors: [knobBottom.cgColor, knobBottom.blended(withFraction: 0.5, of: knobTop2).cgColor, knobTop2.cgColor, knobTop2.blended(withFraction: 0.5, of: knobLight).cgColor, knobLight.cgColor] as CFArray, locations: [0, 0.23, 0.41, 0.73, 1])! 44 | let lowerKnobGradient2 = CGGradient(colorsSpace: nil, colors: [knobTop2.cgColor, knobBottom.cgColor, knobLight.cgColor] as CFArray, locations: [0, 0.51, 1])! 45 | 46 | //// Shadow Declarations 47 | let shadow2 = NSShadow() 48 | shadow2.shadowColor = UIColor.black.withAlphaComponent(0.46) 49 | shadow2.shadowOffset = CGSize(width: 2, height: 8) 50 | shadow2.shadowBlurRadius = 5 51 | let shadow3 = NSShadow() 52 | shadow3.shadowColor = knobLight.withAlphaComponent(0.41 * knobLight.cgColor.alpha) 53 | shadow3.shadowOffset = CGSize(width: 0, height: 10) 54 | shadow3.shadowBlurRadius = 5 55 | let shadow4 = NSShadow() 56 | shadow4.shadowColor = shadowColor 57 | shadow4.shadowOffset = CGSize(width: 0, height: 0) 58 | shadow4.shadowBlurRadius = 12 59 | 60 | //// Variable Declarations 61 | let knobAngle: CGFloat = -240 * knobValue 62 | 63 | //// Knob 64 | context.saveGState() 65 | context.translateBy(x: 3, y: 3) 66 | 67 | 68 | 69 | //// BlackBackground Drawing 70 | let blackBackgroundPath = UIBezierPath(ovalIn: CGRect(x: -1, y: -1, width: 120, height: 120)) 71 | black.setFill() 72 | blackBackgroundPath.fill() 73 | 74 | 75 | //// GradientKnob 2 Drawing 76 | let gradientKnob2Path = UIBezierPath(ovalIn: CGRect(x: 8, y: 8, width: 102, height: 102)) 77 | context.saveGState() 78 | gradientKnob2Path.addClip() 79 | context.drawLinearGradient(lowerKnobGradient2, start: CGPoint(x: 59, y: 110), end: CGPoint(x: 59, y: 8), options: []) 80 | context.restoreGState() 81 | 82 | 83 | //// GradientKnob Drawing 84 | let gradientKnobPath = UIBezierPath(ovalIn: CGRect(x: 14, y: 14, width: 90, height: 90)) 85 | context.saveGState() 86 | context.setShadow(offset: CGSize(width: shadow2.shadowOffset.width * resizedShadowScale, height: shadow2.shadowOffset.height * resizedShadowScale), blur: shadow2.shadowBlurRadius * resizedShadowScale, color: (shadow2.shadowColor as! UIColor).cgColor) 87 | context.beginTransparencyLayer(auxiliaryInfo: nil) 88 | gradientKnobPath.addClip() 89 | context.drawLinearGradient(edge2, start: CGPoint(x: 59, y: 104), end: CGPoint(x: 59, y: 14), options: []) 90 | context.endTransparencyLayer() 91 | 92 | ////// GradientKnob Inner Shadow 93 | context.saveGState() 94 | context.clip(to: gradientKnobPath.bounds) 95 | context.setShadow(offset: CGSize.zero, blur: 0) 96 | context.setAlpha((shadow3.shadowColor as! UIColor).cgColor.alpha) 97 | context.beginTransparencyLayer(auxiliaryInfo: nil) 98 | let gradientKnobOpaqueShadow = (shadow3.shadowColor as! UIColor).withAlphaComponent(1) 99 | context.setShadow(offset: CGSize(width: shadow3.shadowOffset.width * resizedShadowScale, height: shadow3.shadowOffset.height * resizedShadowScale), blur: shadow3.shadowBlurRadius * resizedShadowScale, color: gradientKnobOpaqueShadow.cgColor) 100 | context.setBlendMode(.sourceOut) 101 | context.beginTransparencyLayer(auxiliaryInfo: nil) 102 | 103 | gradientKnobOpaqueShadow.setFill() 104 | gradientKnobPath.fill() 105 | 106 | context.endTransparencyLayer() 107 | context.endTransparencyLayer() 108 | context.restoreGState() 109 | 110 | context.restoreGState() 111 | 112 | 113 | 114 | //// IndicatorGroup 115 | //// Indicator Drawing 116 | context.saveGState() 117 | context.translateBy(x: 59, y: 59) 118 | context.rotate(by: -(knobAngle - 240) * CGFloat.pi/180) 119 | 120 | let indicatorPath = UIBezierPath(rect: CGRect(x: -3, y: -45, width: 6, height: 17)) 121 | context.saveGState() 122 | context.setShadow(offset: CGSize(width: shadow4.shadowOffset.width * resizedShadowScale, height: shadow4.shadowOffset.height * resizedShadowScale), blur: shadow4.shadowBlurRadius * resizedShadowScale, color: (shadow4.shadowColor as! UIColor).cgColor) 123 | orange2.setFill() 124 | indicatorPath.fill() 125 | context.restoreGState() 126 | 127 | 128 | context.restoreGState() 129 | 130 | 131 | 132 | 133 | 134 | context.restoreGState() 135 | 136 | context.restoreGState() 137 | 138 | } 139 | 140 | 141 | 142 | 143 | @objc(KnobStyleKitResizingBehavior) 144 | public enum ResizingBehavior: Int { 145 | case aspectFit /// The content is proportionally resized to fit into the target rectangle. 146 | case aspectFill /// The content is proportionally resized to completely fill the target rectangle. 147 | case stretch /// The content is stretched to match the entire target rectangle. 148 | case center /// The content is centered in the target rectangle, but it is NOT resized. 149 | 150 | public func apply(rect: CGRect, target: CGRect) -> CGRect { 151 | if rect == target || target == CGRect.zero { 152 | return rect 153 | } 154 | 155 | var scales = CGSize.zero 156 | scales.width = abs(target.width / rect.width) 157 | scales.height = abs(target.height / rect.height) 158 | 159 | switch self { 160 | case .aspectFit: 161 | scales.width = min(scales.width, scales.height) 162 | scales.height = scales.width 163 | case .aspectFill: 164 | scales.width = max(scales.width, scales.height) 165 | scales.height = scales.width 166 | case .stretch: 167 | break 168 | case .center: 169 | scales.width = 1 170 | scales.height = 1 171 | } 172 | 173 | var result = rect.standardized 174 | result.size.width *= scales.width 175 | result.size.height *= scales.height 176 | result.origin.x = target.minX + (target.width - result.width) / 2 177 | result.origin.y = target.minY + (target.height - result.height) / 2 178 | return result 179 | } 180 | } 181 | } 182 | 183 | 184 | 185 | private extension UIColor { 186 | func blended(withFraction fraction: CGFloat, of color: UIColor) -> UIColor { 187 | var r1: CGFloat = 1, g1: CGFloat = 1, b1: CGFloat = 1, a1: CGFloat = 1 188 | var r2: CGFloat = 1, g2: CGFloat = 1, b2: CGFloat = 1, a2: CGFloat = 1 189 | 190 | self.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) 191 | color.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) 192 | 193 | return UIColor(red: r1 * (1 - fraction) + r2 * fraction, 194 | green: g1 * (1 - fraction) + g2 * fraction, 195 | blue: b1 * (1 - fraction) + b2 * fraction, 196 | alpha: a1 * (1 - fraction) + a2 * fraction); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /KnobView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KnobView.swift 3 | // SynthUISpike 4 | // 5 | // Created by Matthew Fecher on 7/20/17. 6 | // Copyright © 2017 Matthew Fecher. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol KnobDelegate { 12 | func updateKnobValue(_ value: Double, tag: Int) 13 | } 14 | 15 | @IBDesignable 16 | class KnobView: UIView { 17 | 18 | var delegate: KnobDelegate? 19 | 20 | var minimum = 0.0 { 21 | didSet { 22 | self.knobValue = CGFloat((value - minimum) / (maximum - minimum)) 23 | } 24 | } 25 | var maximum = 1.0 { 26 | didSet { 27 | self.knobValue = CGFloat((value - minimum) / (maximum - minimum)) 28 | } 29 | } 30 | 31 | var value: Double = 0.5 { 32 | didSet { 33 | if value > maximum { 34 | value = maximum 35 | } 36 | if value < minimum { 37 | value = minimum 38 | } 39 | self.knobValue = CGFloat((value - minimum) / (maximum - minimum)) 40 | } 41 | } 42 | 43 | // Knob properties 44 | var knobValue: CGFloat = 0.5 { 45 | didSet { 46 | setNeedsDisplay() 47 | 48 | } 49 | } 50 | var knobFill: CGFloat = 0 51 | var knobSensitivity = 0.005 52 | var lastX: CGFloat = 0 53 | var lastY: CGFloat = 0 54 | 55 | // Init / Lifecycle 56 | override init(frame: CGRect) { 57 | super.init(frame: frame) 58 | contentMode = .redraw 59 | } 60 | 61 | required init?(coder: NSCoder) { 62 | super.init(coder: coder) 63 | self.isUserInteractionEnabled = true 64 | contentMode = .redraw 65 | } 66 | 67 | override func prepareForInterfaceBuilder() { 68 | super.prepareForInterfaceBuilder() 69 | 70 | contentMode = .scaleAspectFit 71 | clipsToBounds = true 72 | } 73 | 74 | class override var requiresConstraintBasedLayout: Bool { 75 | return true 76 | } 77 | 78 | override func draw(_ rect: CGRect) { 79 | KnobStyleKit.drawKnobOne(frame: CGRect(x:0,y:0, width: self.bounds.width, height: self.bounds.height), knobValue: knobValue) 80 | } 81 | 82 | // Helper 83 | func setPercentagesWithTouchPoint(_ touchPoint: CGPoint) { 84 | // Knobs assume up or right is increasing, and down or left is decreasing 85 | 86 | let horizontalChange = Double(touchPoint.x - lastX) * knobSensitivity 87 | value += horizontalChange * (maximum - minimum) 88 | 89 | let verticalChange = Double(touchPoint.y - lastY) * knobSensitivity 90 | value -= verticalChange * (maximum - minimum) 91 | 92 | lastX = touchPoint.x 93 | lastY = touchPoint.y 94 | 95 | delegate?.updateKnobValue(value, tag: self.tag) 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SynthKnobExample 2 | 3 | Simple Example of AudioKit Synth Knobs. Vector based. 4 | 5 | ![screenshot](http://imgur.com/ebuE9sp.png) 6 | 7 | PaintCode file is included in main directory. 8 | Resize in Interface Builder! 9 | 10 | ![knob in ib](https://i.imgflip.com/1svkul.gif) 11 | 12 | For an example of Knobs using callbacks (instead of delegate pattern) see this repo: 13 | [CallbackKnob](https://github.com/swiftcodex/CallbackKnob) 14 | -------------------------------------------------------------------------------- /SynthUISpike.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 942879671F216F94001C75FF /* KnobStyleKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942879661F216F94001C75FF /* KnobStyleKit.swift */; }; 11 | 944156D61F2121CC00C139B9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944156D51F2121CC00C139B9 /* AppDelegate.swift */; }; 12 | 944156D81F2121CC00C139B9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944156D71F2121CC00C139B9 /* ViewController.swift */; }; 13 | 944156DB1F2121CC00C139B9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 944156D91F2121CC00C139B9 /* Main.storyboard */; }; 14 | 944156DD1F2121CC00C139B9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 944156DC1F2121CC00C139B9 /* Assets.xcassets */; }; 15 | 944156E01F2121CC00C139B9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 944156DE1F2121CC00C139B9 /* LaunchScreen.storyboard */; }; 16 | 944156EA1F21231C00C139B9 /* KnobView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944156E91F21231C00C139B9 /* KnobView.swift */; }; 17 | 944156F01F21254600C139B9 /* Knob+Touches.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944156ED1F21254600C139B9 /* Knob+Touches.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 942879661F216F94001C75FF /* KnobStyleKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KnobStyleKit.swift; sourceTree = ""; }; 22 | 944156D21F2121CC00C139B9 /* SynthUISpike.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SynthUISpike.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 944156D51F2121CC00C139B9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 24 | 944156D71F2121CC00C139B9 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 25 | 944156DA1F2121CC00C139B9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 944156DC1F2121CC00C139B9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | 944156DF1F2121CC00C139B9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | 944156E11F2121CC00C139B9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 944156E91F21231C00C139B9 /* KnobView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KnobView.swift; sourceTree = ""; }; 30 | 944156ED1F21254600C139B9 /* Knob+Touches.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Knob+Touches.swift"; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 944156CF1F2121CC00C139B9 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 944156C91F2121CC00C139B9 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 944156EB1F21253400C139B9 /* Knobs */, 48 | 944156D41F2121CC00C139B9 /* SynthUISpike */, 49 | 944156D31F2121CC00C139B9 /* Products */, 50 | ); 51 | sourceTree = ""; 52 | }; 53 | 944156D31F2121CC00C139B9 /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | 944156D21F2121CC00C139B9 /* SynthUISpike.app */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | 944156D41F2121CC00C139B9 /* SynthUISpike */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 944156D51F2121CC00C139B9 /* AppDelegate.swift */, 65 | 944156D71F2121CC00C139B9 /* ViewController.swift */, 66 | 944156D91F2121CC00C139B9 /* Main.storyboard */, 67 | 944156DC1F2121CC00C139B9 /* Assets.xcassets */, 68 | 944156DE1F2121CC00C139B9 /* LaunchScreen.storyboard */, 69 | 944156E11F2121CC00C139B9 /* Info.plist */, 70 | ); 71 | path = SynthUISpike; 72 | sourceTree = ""; 73 | }; 74 | 944156EB1F21253400C139B9 /* Knobs */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 942879661F216F94001C75FF /* KnobStyleKit.swift */, 78 | 944156E91F21231C00C139B9 /* KnobView.swift */, 79 | 944156ED1F21254600C139B9 /* Knob+Touches.swift */, 80 | ); 81 | name = Knobs; 82 | sourceTree = ""; 83 | }; 84 | /* End PBXGroup section */ 85 | 86 | /* Begin PBXNativeTarget section */ 87 | 944156D11F2121CC00C139B9 /* SynthUISpike */ = { 88 | isa = PBXNativeTarget; 89 | buildConfigurationList = 944156E41F2121CC00C139B9 /* Build configuration list for PBXNativeTarget "SynthUISpike" */; 90 | buildPhases = ( 91 | 944156CE1F2121CC00C139B9 /* Sources */, 92 | 944156CF1F2121CC00C139B9 /* Frameworks */, 93 | 944156D01F2121CC00C139B9 /* Resources */, 94 | ); 95 | buildRules = ( 96 | ); 97 | dependencies = ( 98 | ); 99 | name = SynthUISpike; 100 | productName = SynthUISpike; 101 | productReference = 944156D21F2121CC00C139B9 /* SynthUISpike.app */; 102 | productType = "com.apple.product-type.application"; 103 | }; 104 | /* End PBXNativeTarget section */ 105 | 106 | /* Begin PBXProject section */ 107 | 944156CA1F2121CC00C139B9 /* Project object */ = { 108 | isa = PBXProject; 109 | attributes = { 110 | LastSwiftUpdateCheck = 0830; 111 | LastUpgradeCheck = 0830; 112 | ORGANIZATIONNAME = "Matthew Fecher"; 113 | TargetAttributes = { 114 | 944156D11F2121CC00C139B9 = { 115 | CreatedOnToolsVersion = 8.3.3; 116 | LastSwiftMigration = 0830; 117 | ProvisioningStyle = Automatic; 118 | }; 119 | }; 120 | }; 121 | buildConfigurationList = 944156CD1F2121CC00C139B9 /* Build configuration list for PBXProject "SynthUISpike" */; 122 | compatibilityVersion = "Xcode 3.2"; 123 | developmentRegion = English; 124 | hasScannedForEncodings = 0; 125 | knownRegions = ( 126 | en, 127 | Base, 128 | ); 129 | mainGroup = 944156C91F2121CC00C139B9; 130 | productRefGroup = 944156D31F2121CC00C139B9 /* Products */; 131 | projectDirPath = ""; 132 | projectRoot = ""; 133 | targets = ( 134 | 944156D11F2121CC00C139B9 /* SynthUISpike */, 135 | ); 136 | }; 137 | /* End PBXProject section */ 138 | 139 | /* Begin PBXResourcesBuildPhase section */ 140 | 944156D01F2121CC00C139B9 /* Resources */ = { 141 | isa = PBXResourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | 944156E01F2121CC00C139B9 /* LaunchScreen.storyboard in Resources */, 145 | 944156DD1F2121CC00C139B9 /* Assets.xcassets in Resources */, 146 | 944156DB1F2121CC00C139B9 /* Main.storyboard in Resources */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXResourcesBuildPhase section */ 151 | 152 | /* Begin PBXSourcesBuildPhase section */ 153 | 944156CE1F2121CC00C139B9 /* Sources */ = { 154 | isa = PBXSourcesBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | 944156D81F2121CC00C139B9 /* ViewController.swift in Sources */, 158 | 944156D61F2121CC00C139B9 /* AppDelegate.swift in Sources */, 159 | 942879671F216F94001C75FF /* KnobStyleKit.swift in Sources */, 160 | 944156F01F21254600C139B9 /* Knob+Touches.swift in Sources */, 161 | 944156EA1F21231C00C139B9 /* KnobView.swift in Sources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXSourcesBuildPhase section */ 166 | 167 | /* Begin PBXVariantGroup section */ 168 | 944156D91F2121CC00C139B9 /* Main.storyboard */ = { 169 | isa = PBXVariantGroup; 170 | children = ( 171 | 944156DA1F2121CC00C139B9 /* Base */, 172 | ); 173 | name = Main.storyboard; 174 | sourceTree = ""; 175 | }; 176 | 944156DE1F2121CC00C139B9 /* LaunchScreen.storyboard */ = { 177 | isa = PBXVariantGroup; 178 | children = ( 179 | 944156DF1F2121CC00C139B9 /* Base */, 180 | ); 181 | name = LaunchScreen.storyboard; 182 | sourceTree = ""; 183 | }; 184 | /* End PBXVariantGroup section */ 185 | 186 | /* Begin XCBuildConfiguration section */ 187 | 944156E21F2121CC00C139B9 /* Debug */ = { 188 | isa = XCBuildConfiguration; 189 | buildSettings = { 190 | ALWAYS_SEARCH_USER_PATHS = NO; 191 | CLANG_ANALYZER_NONNULL = YES; 192 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 193 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 194 | CLANG_CXX_LIBRARY = "libc++"; 195 | CLANG_ENABLE_MODULES = YES; 196 | CLANG_ENABLE_OBJC_ARC = YES; 197 | CLANG_WARN_BOOL_CONVERSION = YES; 198 | CLANG_WARN_CONSTANT_CONVERSION = YES; 199 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 200 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 201 | CLANG_WARN_EMPTY_BODY = YES; 202 | CLANG_WARN_ENUM_CONVERSION = YES; 203 | CLANG_WARN_INFINITE_RECURSION = YES; 204 | CLANG_WARN_INT_CONVERSION = YES; 205 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 206 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 207 | CLANG_WARN_UNREACHABLE_CODE = YES; 208 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 209 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 210 | COPY_PHASE_STRIP = NO; 211 | DEBUG_INFORMATION_FORMAT = dwarf; 212 | ENABLE_STRICT_OBJC_MSGSEND = YES; 213 | ENABLE_TESTABILITY = YES; 214 | GCC_C_LANGUAGE_STANDARD = gnu99; 215 | GCC_DYNAMIC_NO_PIC = NO; 216 | GCC_NO_COMMON_BLOCKS = YES; 217 | GCC_OPTIMIZATION_LEVEL = 0; 218 | GCC_PREPROCESSOR_DEFINITIONS = ( 219 | "DEBUG=1", 220 | "$(inherited)", 221 | ); 222 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 223 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 224 | GCC_WARN_UNDECLARED_SELECTOR = YES; 225 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 226 | GCC_WARN_UNUSED_FUNCTION = YES; 227 | GCC_WARN_UNUSED_VARIABLE = YES; 228 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 229 | MTL_ENABLE_DEBUG_INFO = YES; 230 | ONLY_ACTIVE_ARCH = YES; 231 | SDKROOT = iphoneos; 232 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 233 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 234 | TARGETED_DEVICE_FAMILY = "1,2"; 235 | }; 236 | name = Debug; 237 | }; 238 | 944156E31F2121CC00C139B9 /* Release */ = { 239 | isa = XCBuildConfiguration; 240 | buildSettings = { 241 | ALWAYS_SEARCH_USER_PATHS = NO; 242 | CLANG_ANALYZER_NONNULL = YES; 243 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 244 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 245 | CLANG_CXX_LIBRARY = "libc++"; 246 | CLANG_ENABLE_MODULES = YES; 247 | CLANG_ENABLE_OBJC_ARC = YES; 248 | CLANG_WARN_BOOL_CONVERSION = YES; 249 | CLANG_WARN_CONSTANT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 258 | CLANG_WARN_UNREACHABLE_CODE = YES; 259 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 260 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 261 | COPY_PHASE_STRIP = NO; 262 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 263 | ENABLE_NS_ASSERTIONS = NO; 264 | ENABLE_STRICT_OBJC_MSGSEND = YES; 265 | GCC_C_LANGUAGE_STANDARD = gnu99; 266 | GCC_NO_COMMON_BLOCKS = YES; 267 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 268 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 269 | GCC_WARN_UNDECLARED_SELECTOR = YES; 270 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 271 | GCC_WARN_UNUSED_FUNCTION = YES; 272 | GCC_WARN_UNUSED_VARIABLE = YES; 273 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 274 | MTL_ENABLE_DEBUG_INFO = NO; 275 | SDKROOT = iphoneos; 276 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 277 | TARGETED_DEVICE_FAMILY = "1,2"; 278 | VALIDATE_PRODUCT = YES; 279 | }; 280 | name = Release; 281 | }; 282 | 944156E51F2121CC00C139B9 /* Debug */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 286 | INFOPLIST_FILE = SynthUISpike/Info.plist; 287 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 288 | PRODUCT_BUNDLE_IDENTIFIER = com.audiokitpro.SynthUISpike; 289 | PRODUCT_NAME = "$(TARGET_NAME)"; 290 | SWIFT_VERSION = 3.0; 291 | }; 292 | name = Debug; 293 | }; 294 | 944156E61F2121CC00C139B9 /* Release */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 298 | INFOPLIST_FILE = SynthUISpike/Info.plist; 299 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 300 | PRODUCT_BUNDLE_IDENTIFIER = com.audiokitpro.SynthUISpike; 301 | PRODUCT_NAME = "$(TARGET_NAME)"; 302 | SWIFT_VERSION = 3.0; 303 | }; 304 | name = Release; 305 | }; 306 | /* End XCBuildConfiguration section */ 307 | 308 | /* Begin XCConfigurationList section */ 309 | 944156CD1F2121CC00C139B9 /* Build configuration list for PBXProject "SynthUISpike" */ = { 310 | isa = XCConfigurationList; 311 | buildConfigurations = ( 312 | 944156E21F2121CC00C139B9 /* Debug */, 313 | 944156E31F2121CC00C139B9 /* Release */, 314 | ); 315 | defaultConfigurationIsVisible = 0; 316 | defaultConfigurationName = Release; 317 | }; 318 | 944156E41F2121CC00C139B9 /* Build configuration list for PBXNativeTarget "SynthUISpike" */ = { 319 | isa = XCConfigurationList; 320 | buildConfigurations = ( 321 | 944156E51F2121CC00C139B9 /* Debug */, 322 | 944156E61F2121CC00C139B9 /* Release */, 323 | ); 324 | defaultConfigurationIsVisible = 0; 325 | defaultConfigurationName = Release; 326 | }; 327 | /* End XCConfigurationList section */ 328 | }; 329 | rootObject = 944156CA1F2121CC00C139B9 /* Project object */; 330 | } 331 | -------------------------------------------------------------------------------- /SynthUISpike.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SynthUISpike.xcodeproj/xcuserdata/wu.xcuserdatad/xcschemes/SynthUISpike.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /SynthUISpike.xcodeproj/xcuserdata/wu.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SynthUISpike.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 944156D11F2121CC00C139B9 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /SynthUISpike/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SynthUISpike 4 | // 5 | // Created by Matthew Fecher on 7/20/17. 6 | // Copyright © 2017 Matthew Fecher. 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: [UIApplicationLaunchOptionsKey: 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 | -------------------------------------------------------------------------------- /SynthUISpike/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 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /SynthUISpike/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 | -------------------------------------------------------------------------------- /SynthUISpike/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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /SynthUISpike/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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarHidden 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationLandscapeLeft 36 | UIInterfaceOrientationLandscapeRight 37 | 38 | UISupportedInterfaceOrientations~ipad 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationPortraitUpsideDown 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /SynthUISpike/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SynthUISpike 4 | // 5 | // Created by Matthew Fecher on 7/20/17. 6 | // Copyright © 2017 Matthew Fecher. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | @IBOutlet weak var knobOne: KnobView! 14 | @IBOutlet weak var knobTwo: KnobView! 15 | @IBOutlet weak var displayLabel: UILabel! 16 | 17 | enum ControlTag: Int { 18 | case control1 = 100 19 | case control2 = 101 20 | } 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | // Do any additional setup after loading the view, typically from a nib. 25 | 26 | knobOne.delegate = self 27 | knobTwo.delegate = self 28 | } 29 | 30 | 31 | } 32 | 33 | 34 | //***************************************************************** 35 | // MARK: - 🎛 Knob Delegate 36 | //***************************************************************** 37 | 38 | extension ViewController: KnobDelegate { 39 | 40 | func updateKnobValue(_ value: Double, tag: Int) { 41 | 42 | switch (tag) { 43 | 44 | // VCOs 45 | case ControlTag.control1.rawValue: 46 | let intValue = Int(floor(value * 10)) 47 | let message = "Knob One: \(intValue)" 48 | displayLabel.text = message 49 | 50 | case ControlTag.control2.rawValue: 51 | let message = "Knob Two: \(String(format: "%.02f", value))" 52 | displayLabel.text = message 53 | 54 | default: 55 | break 56 | } 57 | } 58 | } 59 | --------------------------------------------------------------------------------