Skip to content

Commit 4d37e33

Browse files
committed
synth knob
1 parent 815e5ae commit 4d37e33

File tree

8 files changed

+241
-108
lines changed

8 files changed

+241
-108
lines changed

Knob+Touches.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import UIKit
1010

11-
extension Knob {
11+
extension KnobView {
1212

1313
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
1414
for touch in touches {

KnobStyleKit.swift

+66-94
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// KnobStyleKit.swift
33
// UISpike
44
//
5-
// Created by Matthew Fecher on 7/20/17.
5+
// Created by Matthew Fecher on 7/21/17.
66
// Copyright © 2017 AudioKit. All rights reserved.
77
//
88
// Generated by PaintCode
@@ -12,160 +12,132 @@
1212
//
1313

1414

15-
16-
import Cocoa
15+
import UIKit
1716

1817
public class KnobStyleKit : NSObject {
1918

2019
//// Drawing Methods
2120

22-
@objc dynamic public class func drawKnobCanvas(frame targetFrame: NSRect = NSRect(x: 0, y: 0, width: 120, height: 120), resizing: ResizingBehavior = .aspectFit, knobValue: CGFloat = 1) {
21+
@objc dynamic public class func drawKnobOne(frame: CGRect = CGRect(x: 39, y: 40, width: 120, height: 120), knobValue: CGFloat = 0.794) {
2322
//// General Declarations
24-
let context = NSGraphicsContext.current()!.cgContext
25-
26-
//// Resize to Target Frame
27-
NSGraphicsContext.saveGraphicsState()
28-
let resizedFrame: NSRect = resizing.apply(rect: NSRect(x: 0, y: 0, width: 120, height: 120), target: targetFrame)
29-
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
30-
context.scaleBy(x: resizedFrame.width / 120, y: resizedFrame.height / 120)
31-
let resizedShadowScale: CGFloat = min(resizedFrame.width / 120, resizedFrame.height / 120)
32-
23+
let context = UIGraphicsGetCurrentContext()!
3324

3425
//// Color Declarations
35-
let black = NSColor(red: 0, green: 0, blue: 0, alpha: 1)
36-
let shadowColor = NSColor(red: 1, green: 0.596, blue: 0, alpha: 1)
37-
let knobBottom = NSColor(red: 0.18, green: 0.18, blue: 0.192, alpha: 1)
38-
let knobLight = NSColor(red: 0.498, green: 0.498, blue: 0.51, alpha: 1)
39-
let knobTop2 = NSColor(red: 0.141, green: 0.141, blue: 0.161, alpha: 1)
40-
let orange2 = NSColor(red: 0.902, green: 0.533, blue: 0.008, alpha: 1)
26+
let black = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)
27+
let shadowColor = UIColor(red: 1.000, green: 0.596, blue: 0.000, alpha: 1.000)
28+
let knobBottom = UIColor(red: 0.180, green: 0.180, blue: 0.192, alpha: 1.000)
29+
let knobLight = UIColor(red: 0.498, green: 0.498, blue: 0.510, alpha: 1.000)
30+
let knobTop2 = UIColor(red: 0.141, green: 0.141, blue: 0.161, alpha: 1.000)
31+
let orange2 = UIColor(red: 0.902, green: 0.533, blue: 0.008, alpha: 1.000)
4132

4233
//// Gradient Declarations
43-
let edge2 = NSGradient(colors: [knobBottom, knobBottom.blended(withFraction: 0.5, of: knobTop2)!, knobTop2, knobTop2.blended(withFraction: 0.5, of: knobLight)!, knobLight], atLocations: [0.0, 0.23, 0.41, 0.73, 1.0], colorSpace: NSColorSpace.sRGB)!
44-
let lowerKnobGradient2 = NSGradient(colors: [knobTop2, knobBottom, knobLight], atLocations: [0.0, 0.51, 1.0], colorSpace: NSColorSpace.sRGB)!
34+
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])!
35+
let lowerKnobGradient2 = CGGradient(colorsSpace: nil, colors: [knobTop2.cgColor, knobBottom.cgColor, knobLight.cgColor] as CFArray, locations: [0, 0.51, 1])!
4536

4637
//// Shadow Declarations
4738
let shadow2 = NSShadow()
48-
shadow2.shadowColor = NSColor.black.withAlphaComponent(0.46)
49-
shadow2.shadowOffset = NSSize(width: 2, height: -8)
39+
shadow2.shadowColor = UIColor.black.withAlphaComponent(0.46)
40+
shadow2.shadowOffset = CGSize(width: 2, height: 8)
5041
shadow2.shadowBlurRadius = 5
5142
let shadow3 = NSShadow()
52-
shadow3.shadowColor = knobLight.withAlphaComponent(0.41 * knobLight.alphaComponent)
53-
shadow3.shadowOffset = NSSize(width: 0, height: -10)
43+
shadow3.shadowColor = knobLight.withAlphaComponent(0.41 * knobLight.cgColor.alpha)
44+
shadow3.shadowOffset = CGSize(width: 0, height: 10)
5445
shadow3.shadowBlurRadius = 5
5546
let shadow4 = NSShadow()
5647
shadow4.shadowColor = shadowColor
57-
shadow4.shadowOffset = NSSize(width: 0, height: 0)
48+
shadow4.shadowOffset = CGSize(width: 0, height: 0)
5849
shadow4.shadowBlurRadius = 12
5950

6051
//// Variable Declarations
6152
let knobAngle: CGFloat = -240 * knobValue
6253

6354
//// Knob
55+
context.saveGState()
56+
context.translateBy(x: frame.minX + 0.50000 * frame.width, y: frame.minY + 0.50000 * frame.height)
57+
58+
6459
//// BlackBackground Drawing
65-
let blackBackgroundPath = NSBezierPath(ovalIn: NSRect(x: 0, y: 0, width: 120, height: 120))
60+
let blackBackgroundPath = UIBezierPath(ovalIn: CGRect(x: -60, y: -60, width: 120, height: 120))
6661
black.setFill()
6762
blackBackgroundPath.fill()
6863

6964

7065
//// GradientKnob 2 Drawing
71-
let gradientKnob2Path = NSBezierPath(ovalIn: NSRect(x: 9, y: 9, width: 102, height: 102))
72-
lowerKnobGradient2.draw(in: gradientKnob2Path, angle: 90)
66+
let gradientKnob2Path = UIBezierPath(ovalIn: CGRect(x: -51, y: -51, width: 102, height: 102))
67+
context.saveGState()
68+
gradientKnob2Path.addClip()
69+
context.drawLinearGradient(lowerKnobGradient2, start: CGPoint(x: -0, y: 51), end: CGPoint(x: 0, y: -51), options: [])
70+
context.restoreGState()
7371

7472

7573
//// GradientKnob Drawing
76-
let gradientKnobPath = NSBezierPath(ovalIn: NSRect(x: 15, y: 15, width: 90, height: 90))
77-
NSGraphicsContext.saveGraphicsState()
78-
context.setShadow(offset: NSSize(width: shadow2.shadowOffset.width * resizedShadowScale, height: shadow2.shadowOffset.height * resizedShadowScale), blur: shadow2.shadowBlurRadius * resizedShadowScale, color: shadow2.shadowColor!.cgColor)
74+
let gradientKnobPath = UIBezierPath(ovalIn: CGRect(x: -45, y: -45, width: 90, height: 90))
75+
context.saveGState()
76+
context.setShadow(offset: shadow2.shadowOffset, blur: shadow2.shadowBlurRadius, color: (shadow2.shadowColor as! UIColor).cgColor)
7977
context.beginTransparencyLayer(auxiliaryInfo: nil)
80-
edge2.draw(in: gradientKnobPath, angle: 90)
78+
gradientKnobPath.addClip()
79+
context.drawLinearGradient(edge2, start: CGPoint(x: -0, y: 45), end: CGPoint(x: 0, y: -45), options: [])
8180
context.endTransparencyLayer()
8281

8382
////// GradientKnob Inner Shadow
84-
NSGraphicsContext.saveGraphicsState()
85-
NSRectClip(gradientKnobPath.bounds)
86-
context.setShadow(offset: NSSize.zero, blur: 0, color: nil)
87-
88-
context.setAlpha(shadow3.shadowColor!.alphaComponent)
83+
context.saveGState()
84+
context.clip(to: gradientKnobPath.bounds)
85+
context.setShadow(offset: CGSize.zero, blur: 0)
86+
context.setAlpha((shadow3.shadowColor as! UIColor).cgColor.alpha)
8987
context.beginTransparencyLayer(auxiliaryInfo: nil)
90-
let gradientKnobOpaqueShadow = NSShadow()
91-
gradientKnobOpaqueShadow.shadowColor = shadow3.shadowColor!.withAlphaComponent(1)
92-
gradientKnobOpaqueShadow.shadowOffset = NSSize(width: shadow3.shadowOffset.width * resizedShadowScale, height: shadow3.shadowOffset.height * resizedShadowScale)
93-
gradientKnobOpaqueShadow.shadowBlurRadius = shadow3.shadowBlurRadius * resizedShadowScale
94-
gradientKnobOpaqueShadow.set()
95-
88+
let gradientKnobOpaqueShadow = (shadow3.shadowColor as! UIColor).withAlphaComponent(1)
89+
context.setShadow(offset: shadow3.shadowOffset, blur: shadow3.shadowBlurRadius, color: gradientKnobOpaqueShadow.cgColor)
9690
context.setBlendMode(.sourceOut)
9791
context.beginTransparencyLayer(auxiliaryInfo: nil)
9892

99-
gradientKnobOpaqueShadow.shadowColor!.setFill()
93+
gradientKnobOpaqueShadow.setFill()
10094
gradientKnobPath.fill()
10195

10296
context.endTransparencyLayer()
10397
context.endTransparencyLayer()
104-
NSGraphicsContext.restoreGraphicsState()
98+
context.restoreGState()
10599

106-
NSGraphicsContext.restoreGraphicsState()
100+
context.restoreGState()
107101

108102

109103

110104
//// IndicatorGroup
111105
//// Indicator Drawing
112-
NSGraphicsContext.saveGraphicsState()
113-
context.translateBy(x: 60, y: 60)
114-
context.rotate(by: (knobAngle - 240) * CGFloat.pi/180)
106+
context.saveGState()
107+
context.rotate(by: -(knobAngle - 240) * CGFloat.pi/180)
115108

116-
let indicatorPath = NSBezierPath(rect: NSRect(x: -3, y: 29, width: 6, height: 17))
117-
NSGraphicsContext.saveGraphicsState()
118-
context.setShadow(offset: NSSize(width: shadow4.shadowOffset.width * resizedShadowScale, height: shadow4.shadowOffset.height * resizedShadowScale), blur: shadow4.shadowBlurRadius * resizedShadowScale, color: shadow4.shadowColor!.cgColor)
109+
let indicatorPath = UIBezierPath(rect: CGRect(x: -3, y: -46, width: 6, height: 17))
110+
context.saveGState()
111+
context.setShadow(offset: shadow4.shadowOffset, blur: shadow4.shadowBlurRadius, color: (shadow4.shadowColor as! UIColor).cgColor)
119112
orange2.setFill()
120113
indicatorPath.fill()
121-
NSGraphicsContext.restoreGraphicsState()
114+
context.restoreGState()
115+
122116

117+
context.restoreGState()
123118

124-
NSGraphicsContext.restoreGraphicsState()
125-
126-
NSGraphicsContext.restoreGraphicsState()
127119

120+
121+
122+
123+
context.restoreGState()
128124
}
129125

126+
}
127+
128+
130129

130+
private extension UIColor {
131+
func blended(withFraction fraction: CGFloat, of color: UIColor) -> UIColor {
132+
var r1: CGFloat = 1, g1: CGFloat = 1, b1: CGFloat = 1, a1: CGFloat = 1
133+
var r2: CGFloat = 1, g2: CGFloat = 1, b2: CGFloat = 1, a2: CGFloat = 1
131134

135+
self.getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
136+
color.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)
132137

133-
@objc(KnobStyleKitResizingBehavior)
134-
public enum ResizingBehavior: Int {
135-
case aspectFit /// The content is proportionally resized to fit into the target rectangle.
136-
case aspectFill /// The content is proportionally resized to completely fill the target rectangle.
137-
case stretch /// The content is stretched to match the entire target rectangle.
138-
case center /// The content is centered in the target rectangle, but it is NOT resized.
139-
140-
public func apply(rect: NSRect, target: NSRect) -> NSRect {
141-
if rect == target || target == NSRect.zero {
142-
return rect
143-
}
144-
145-
var scales = NSSize.zero
146-
scales.width = abs(target.width / rect.width)
147-
scales.height = abs(target.height / rect.height)
148-
149-
switch self {
150-
case .aspectFit:
151-
scales.width = min(scales.width, scales.height)
152-
scales.height = scales.width
153-
case .aspectFill:
154-
scales.width = max(scales.width, scales.height)
155-
scales.height = scales.width
156-
case .stretch:
157-
break
158-
case .center:
159-
scales.width = 1
160-
scales.height = 1
161-
}
162-
163-
var result = rect.standardized
164-
result.size.width *= scales.width
165-
result.size.height *= scales.height
166-
result.origin.x = target.minX + (target.width - result.width) / 2
167-
result.origin.y = target.minY + (target.height - result.height) / 2
168-
return result
169-
}
138+
return UIColor(red: r1 * (1 - fraction) + r2 * fraction,
139+
green: g1 * (1 - fraction) + g2 * fraction,
140+
blue: b1 * (1 - fraction) + b2 * fraction,
141+
alpha: a1 * (1 - fraction) + a2 * fraction);
170142
}
171143
}

KnobView.swift

+75-6
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,83 @@
88

99
import UIKit
1010

11+
@IBDesignable
1112
class KnobView: UIView {
1213

13-
/*
14-
// Only override draw() if you perform custom drawing.
15-
// An empty implementation adversely affects performance during animation.
14+
var minimum = 0.0 {
15+
didSet {
16+
self.knobValue = CGFloat((value - minimum) / (maximum - minimum))
17+
}
18+
}
19+
var maximum = 1.0 {
20+
didSet {
21+
self.knobValue = CGFloat((value - minimum) / (maximum - minimum))
22+
}
23+
}
24+
25+
var value: Double = 0.5 {
26+
didSet {
27+
if value > maximum {
28+
value = maximum
29+
}
30+
if value < minimum {
31+
value = minimum
32+
}
33+
self.knobValue = CGFloat((value - minimum) / (maximum - minimum))
34+
}
35+
}
36+
37+
// Knob properties
38+
var knobValue: CGFloat = 0.5 {
39+
didSet {
40+
setNeedsDisplay()
41+
42+
}
43+
}
44+
var knobFill: CGFloat = 0
45+
var knobSensitivity = 0.005
46+
var lastX: CGFloat = 0
47+
var lastY: CGFloat = 0
48+
49+
// Init / Lifecycle
50+
override init(frame: CGRect) {
51+
super.init(frame: frame)
52+
contentMode = .redraw
53+
}
54+
55+
required init?(coder: NSCoder) {
56+
super.init(coder: coder)
57+
self.isUserInteractionEnabled = true
58+
contentMode = .redraw
59+
}
60+
61+
override func prepareForInterfaceBuilder() {
62+
super.prepareForInterfaceBuilder()
63+
64+
contentMode = .scaleAspectFill
65+
clipsToBounds = true
66+
}
67+
68+
class override var requiresConstraintBasedLayout: Bool {
69+
return true
70+
}
71+
1672
override func draw(_ rect: CGRect) {
17-
// Drawing code
73+
KnobStyleKit.drawKnobOne(knobValue: knobValue)
1874
}
19-
*/
20-
75+
76+
// Helper
77+
func setPercentagesWithTouchPoint(_ touchPoint: CGPoint) {
78+
// Knobs assume up or right is increasing, and down or left is decreasing
79+
80+
let horizontalChange = Double(touchPoint.x - lastX) * knobSensitivity
81+
value += horizontalChange * (maximum - minimum)
82+
83+
let verticalChange = Double(touchPoint.y - lastY) * knobSensitivity
84+
value -= verticalChange * (maximum - minimum)
85+
86+
lastX = touchPoint.x
87+
lastY = touchPoint.y
88+
}
89+
2190
}

0 commit comments

Comments
 (0)