#import "GBSlider.h" #import #if !__has_include() /* Building with older SDKs */ API_AVAILABLE(ios(19.0)) @interface UISliderTrackConfiguration : NSObject @property (nonatomic, readwrite) bool allowsTickValuesOnly; @property (nonatomic, readwrite) float neutralValue; + (instancetype)configurationWithNumberOfTicks:(NSInteger)ticks; @end @interface UISlider (configuration) @property(nonatomic, copy, nullable) UISliderTrackConfiguration *trackConfiguration API_AVAILABLE(ios(19.0)); @end #endif static inline void temperature_tint(double temperature, double *r, double *g, double *b) { if (temperature >= 0) { *r = 1; *g = pow(1 - temperature, 0.375); if (temperature >= 0.75) { *b = 0; } else { *b = sqrt(0.75 - temperature) / sqrt(0.75); } } else { *b = 1; double squared = pow(temperature, 2); *g = 0.125 * squared + 0.3 * temperature + 1.0; *r = 0.21875 * squared + 0.5 * temperature + 1.0; } } @implementation GBSlider { GBSliderStyle _style; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; [self addTarget:self action:@selector(valueChanged) forControlEvents:UIControlEventValueChanged]; self.style = GBSliderStyleTemperature; return self; } - (void)updateTint { if (_style == GBSliderStyleTemperature) { double r, g, b; temperature_tint(self.value, &r, &g, &b); self.thumbTintColor = [UIColor colorWithRed:r green:g blue:b alpha:1.0]; } if (_style == GBSliderStyleHue) { double hue = (self.value - self.minimumValue) / (self.maximumValue - self.minimumValue); double r = 0, g = 0, b =0 ; double t = fmod(hue * 6, 1); switch ((int)(hue * 6) % 6) { case 0: r = 1; g = t; break; case 1: r = 1 - t; g = 1; break; case 2: g = 1; b = t; break; case 3: g = 1 - t; b = 1; break; case 4: b = 1; r = t; break; case 5: b = 1 - t; r = 1; break; } self.thumbTintColor = [UIColor colorWithRed:r green:g blue:b alpha:1.0]; } } - (void)setValue:(float)value { [super setValue:value]; [self updateTint]; } - (void)valueChanged { if (fabsf(self.value) < 0.05 && self.value != 0 && _style != GBSliderStyleHue) { self.value = 0; } [self updateTint]; } -(UIImage *)maximumTrackImageForState:(UIControlState)state { return [[UIImage alloc] init]; } -(UIImage *)minimumTrackImageForState:(UIControlState)state { return [[UIImage alloc] init]; } - (void)drawRect:(CGRect)rect { bool solarium = false; if (@available(iOS 19.0, *)) { solarium = true; } CGSize size = self.bounds.size; UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(2, round(size.height / 2 - 1.5), size.width - 4, 3) cornerRadius:4]; if (_style != GBSliderStyleHue) { [path appendPath:[UIBezierPath bezierPathWithRoundedRect:CGRectMake(round(size.width / 2 - 1.5), 12, 3, size.height - 24) cornerRadius:4]]; } if (_style == GBSliderStyleTicks) { [path appendPath:[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 12, 3, size.height - 24) cornerRadius:4]]; [path appendPath:[UIBezierPath bezierPathWithRoundedRect:CGRectMake(size.width - 3, 12, 3, size.height - 24) cornerRadius:4]]; } if (_style == GBSliderStyleHue) { UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(3, round(size.height / 2 - 1.5) + 1 - solarium, size.width - 6, solarium? 2 : 1) cornerRadius:8]; CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSaveGState(context); [path addClip]; CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); CGColorRef colors[] = { UIColor.redColor.CGColor, UIColor.yellowColor.CGColor, UIColor.greenColor.CGColor, UIColor.cyanColor.CGColor, UIColor.blueColor.CGColor, UIColor.magentaColor.CGColor, UIColor.redColor.CGColor, }; CFArrayRef colorsArray = CFArrayCreate(NULL, (const void **)colors, 7, &kCFTypeArrayCallBacks); CGGradientRef gradient = CGGradientCreateWithColors(colorspace, colorsArray, NULL); unsigned spacing = solarium? 16 : 3; CGContextDrawLinearGradient(context, gradient, (CGPoint){spacing, 0}, (CGPoint){size.width - spacing, 0}, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); CFRelease(gradient); CFRelease(colorsArray); CFRelease(colorspace); CGContextRestoreGState(context); } [[UIColor colorWithRed:120 / 255.0 green:120 / 255.0 blue:130 / 255.0 alpha:70 / 255.0] set]; if (!solarium) { [path fill]; } [super drawRect:rect]; } - (void)setStyle:(GBSliderStyle)style { _style = style; if (@available(iOS 19.0, *)) { switch (_style) { case GBSliderStyleTemperature: case GBSliderStyleTicks: { UISliderTrackConfiguration *conf = [objc_getClass("UISliderTrackConfiguration") configurationWithNumberOfTicks:3]; conf.allowsTickValuesOnly = false; conf.neutralValue = 0.5; self.trackConfiguration = conf; self.maximumTrackTintColor = nil; self.minimumTrackTintColor = nil; break; } case GBSliderStyleHue: { UISliderTrackConfiguration *conf = [objc_getClass("UISliderTrackConfiguration") configurationWithNumberOfTicks:0]; conf.allowsTickValuesOnly = false; self.trackConfiguration = conf; self.minimumTrackTintColor = [UIColor clearColor]; break; } } } } - (GBSliderStyle)style { return _style; } - (void)setFrame:(CGRect)frame { [super setFrame:frame]; [self setNeedsDisplay]; } @end