Skip to content

Commit

Permalink
[Slider] Add tick color API (#3344)
Browse files Browse the repository at this point in the history
The Slider needs a way to support custom colors for the discrete value "ticks"
within both the filled and unfilled portions of the track.  They should also
be customizable across states (to support disabled colors).

**Before**
![slider-tick-ex-before](https://user-images.githubusercontent.com/1753199/38699189-5acc6590-3e65-11e8-8078-a92f7f4b52dc.png)

**After**
![slider-tick-ex-after](https://user-images.githubusercontent.com/1753199/38699194-5eacb89a-3e65-11e8-8b46-c607a013730b.png)


Partially implements #3137
Pivotal story: https://www.pivotaltracker.com/story/show/155525171
14f043b
  • Loading branch information
Robert Moore committed Apr 12, 2018
1 parent f31cd30 commit 3cbd588
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 0 deletions.
18 changes: 18 additions & 0 deletions components/Slider/examples/SliderCollectionViewController.m
Expand Up @@ -30,6 +30,8 @@ @interface MDCSliderModel : NSObject
@property(nonatomic, assign) UIColor *labelColor;
@property(nonatomic, assign) UIColor *bgColor;
@property(nonatomic, nullable) UIColor *sliderColor;
@property(nonatomic, nullable) UIColor *filledTickColor;
@property(nonatomic, nullable) UIColor *backgroundTickColor;
@property(nonatomic, nullable) UIColor *trackBackgroundColor;
@property(nonatomic, assign) int numDiscreteValues;
@property(nonatomic, assign) CGFloat value;
Expand Down Expand Up @@ -112,6 +114,18 @@ - (void)applyModel:(MDCSliderModel *)model {
_slider.inkColor = model.sliderColor;
}

if (model.filledTickColor) {
[_slider setFilledTrackTickColor:model.filledTickColor forState:UIControlStateNormal];
} else {
[_slider setFilledTrackTickColor:UIColor.blackColor forState:UIControlStateNormal];
}

if (model.backgroundTickColor) {
[_slider setBackgroundTrackTickColor:model.backgroundTickColor forState:UIControlStateNormal];
} else {
[_slider setBackgroundTrackTickColor:UIColor.blackColor forState:UIControlStateNormal];
}

// Add target/action pair
[_slider addTarget:model
action:@selector(didChangeMDCSliderValue:)
Expand Down Expand Up @@ -202,13 +216,17 @@ - (instancetype)init {
model.labelString = @"Discrete slider with numeric value label";
model.numDiscreteValues = 5;
model.value = 0.2f;
model.backgroundTickColor = UIColor.blackColor;
model.filledTickColor = MDCPalette.bluePalette.tint100;
[_sliders addObject:model];

model = [[MDCSliderModel alloc] init];
model.labelString = @"Discrete slider without numeric value label";
model.numDiscreteValues = 7;
model.value = 1.f;
model.discreteValueLabel = NO;
model.backgroundTickColor = UIColor.blackColor;
model.filledTickColor = MDCPalette.bluePalette.tint100;
[_sliders addObject:model];

model = [[MDCSliderModel alloc] init];
Expand Down
47 changes: 47 additions & 0 deletions components/Slider/src/MDCSlider.h
Expand Up @@ -114,6 +114,53 @@ IB_DESIGNABLE
*/
- (nullable UIColor *)trackBackgroundColorForState:(UIControlState)state;

/**
Sets the color of the ticks within the filled track to use for the specified state.
In general, if a property is not specified for a state, the default is to use the
@c UIControlStateNormal value. If the @c UIControlStateNormal value is not set, then the property
defaults to a default value. Therefore, at a minimum, you should set the value for the
normal state.
@param tickColor The color of the tick marks within the filled track.
@param state The state of the slider.
*/
- (void)setFilledTrackTickColor:(nullable UIColor *)tickColor forState:(UIControlState)state;

/**
Returns the tick color for the filled track portion associated with the specified state.
@params state The state that uses the filled-track tick color.
@returns The filled-track tick color for the specified state. If no color has been set for the
specific state, this method returns the color associated with the @c UIControlStateNormal
state.
*/
- (nullable UIColor *)filledTrackTickColorForState:(UIControlState)state;

/**
Sets the color of the ticks for the background (unfilled) track to use for the specified state.
In general, if a property is not specified for a state, the default is to use the
@c UIControlStateNormal value. If the @c UIControlStateNormal value is not set, then the property
defaults to a default value. Therefore, at a minimum, you should set the value for the
normal state.
@param tickColor The color of the tick marks outside the filled track.
@param state The state of the slider.
*/
- (void)setBackgroundTrackTickColor:(nullable UIColor *)tickColor forState:(UIControlState)state;

/**
Returns the tick color for the background (unfilled) track portion associated with the specified
state.
@params state The state that uses the background-track tick color.
@returns The background-track tick color for the specified state. If no color has been set for the
specific state, this method returns the color associated with the @c UIControlStateNormal
state.
*/
- (nullable UIColor *)backgroundTrackTickColorForState:(UIControlState)state;

/**
The color of the Ink ripple.
Expand Down
44 changes: 44 additions & 0 deletions components/Slider/src/MDCSlider.m
Expand Up @@ -39,6 +39,8 @@ @implementation MDCSlider {
NSMutableDictionary *_thumbColorsForState;
NSMutableDictionary *_trackFillColorsForState;
NSMutableDictionary *_trackBackgroundColorsForState;
NSMutableDictionary *_filledTickColorsForState;
NSMutableDictionary *_backgroundTickColorsForState;
}

- (instancetype)initWithFrame:(CGRect)frame {
Expand Down Expand Up @@ -97,6 +99,10 @@ - (void)commonMDCSliderInit {
_trackBackgroundColorsForState = [@{} mutableCopy];
_trackBackgroundColorsForState[@(UIControlStateNormal)] = [[self class] defaultTrackOffColor];
_trackBackgroundColorsForState[@(UIControlStateDisabled)] = [[self class] defaultDisabledColor];
_filledTickColorsForState = [@{} mutableCopy];
_filledTickColorsForState[@(UIControlStateNormal)] = UIColor.blackColor;
_backgroundTickColorsForState = [@{} mutableCopy];
_backgroundTickColorsForState[@(UIControlStateNormal)] = UIColor.blackColor;

[self addSubview:_thumbTrack];
}
Expand Down Expand Up @@ -167,6 +173,42 @@ - (UIColor *)thumbColorForState:(UIControlState)state {
return color;
}

- (void)setFilledTrackTickColor:(UIColor *)tickColor forState:(UIControlState)state {
_filledTickColorsForState[@(state)] = tickColor;
if (state == self.state) {
[self updateColorsForState];
}
}

- (UIColor *)filledTrackTickColorForState:(UIControlState)state {
UIColor *color = _filledTickColorsForState[@(state)];
if (color) {
return color;
}
if (state != UIControlStateNormal) {
color = _filledTickColorsForState[@(UIControlStateNormal)];
}
return color;
}

- (void)setBackgroundTrackTickColor:(UIColor *)tickColor forState:(UIControlState)state {
_backgroundTickColorsForState[@(state)] = tickColor;
if (self.state == state) {
[self updateColorsForState];
}
}

- (UIColor *)backgroundTrackTickColorForState:(UIControlState)state {
UIColor *color = _backgroundTickColorsForState[@(state)];
if (color) {
return color;
}
if (state != UIControlStateNormal) {
color = _backgroundTickColorsForState[@(UIControlStateNormal)];
}
return color;
}

- (void)updateColorsForState {
if (!self.isStatefulAPIEnabled) {
return;
Expand All @@ -183,6 +225,8 @@ - (void)updateColorsForState {
// trackOnColor is null_resettable, so explicitly set to `.clear` for the correct effect
_thumbTrack.trackOnColor = [self trackFillColorForState:self.state] ?: UIColor.clearColor;
_thumbTrack.inkColor = self.inkColor;
_thumbTrack.trackOnTickColor = [self filledTrackTickColorForState:self.state];
_thumbTrack.trackOffTickColor = [self backgroundTrackTickColorForState:self.state];
}

#pragma mark - ThumbTrack passthrough methods
Expand Down
174 changes: 174 additions & 0 deletions components/Slider/tests/unit/SliderTests.m
Expand Up @@ -712,6 +712,180 @@ - (void)testSettingTrackBackgroundColorForStateHasNoEffectWhenStatefulAPIDisable
[self.slider trackFillColorForState:UIControlStateDisabled]);
}

#pragma mark - filledTrackTickColorForState

- (void)testFilledTrackTickColorForStateDefaults {
// Then
NSUInteger maximumStateValue = UIControlStateNormal | UIControlStateSelected |
UIControlStateHighlighted | UIControlStateDisabled;
for (NSUInteger state = 0; state <= maximumStateValue; ++state) {
XCTAssertEqualObjects([self.slider filledTrackTickColorForState:state],
UIColor.blackColor);
}
}

- (void)testFilledTrackTickColorForStateFallback {
// When
[self.slider setFilledTrackTickColor:UIColor.orangeColor forState:UIControlStateNormal];

// Then
NSUInteger maximumStateValue = UIControlStateNormal | UIControlStateSelected |
UIControlStateHighlighted | UIControlStateDisabled;
for (NSUInteger state = 0; state <= maximumStateValue; ++state) {
XCTAssertEqualObjects([self.slider filledTrackTickColorForState:state], UIColor.orangeColor,
@"(%@) is not equal to (%@) for state (%ld)",
[self.slider filledTrackTickColorForState:state], UIColor.orangeColor,
(long)state);
}
}

- (void)testFilledTrackTickColorForStateAppliesToThumbTrack {
// Given
self.slider.statefulAPIEnabled = YES;

// When
[self.slider setFilledTrackTickColor:UIColor.purpleColor forState:UIControlStateNormal];
[self.slider setFilledTrackTickColor:UIColor.redColor forState:UIControlStateHighlighted];
[self.slider setFilledTrackTickColor:UIColor.cyanColor forState:UIControlStateSelected];
[self.slider setFilledTrackTickColor:UIColor.grayColor forState:UIControlStateDisabled];
[self.slider setFilledTrackTickColor:UIColor.orangeColor
forState:UIControlStateHighlighted | UIControlStateSelected];
[self.slider setFilledTrackTickColor:UIColor.yellowColor
forState:UIControlStateDisabled | UIControlStateSelected];

// Then
self.slider.enabled = NO;
self.slider.selected = NO;
self.slider.highlighted = NO;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOnTickColor,
[self.slider filledTrackTickColorForState:UIControlStateDisabled]);
self.slider.enabled = NO;
self.slider.selected = YES;
self.slider.highlighted = NO;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOnTickColor,
[self.slider filledTrackTickColorForState:UIControlStateDisabled |
UIControlStateSelected]);
self.slider.enabled = NO;
self.slider.selected = NO;
self.slider.highlighted = YES;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOnTickColor,
[self.slider filledTrackTickColorForState:UIControlStateDisabled]);
self.slider.enabled = NO;
self.slider.selected = YES;
self.slider.highlighted = YES;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOnTickColor,
[self.slider filledTrackTickColorForState:UIControlStateDisabled |
UIControlStateSelected]);
self.slider.enabled = YES;
self.slider.selected = NO;
self.slider.highlighted = NO;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOnTickColor,
[self.slider filledTrackTickColorForState:UIControlStateNormal]);
self.slider.enabled = YES;
self.slider.selected = YES;
self.slider.highlighted = NO;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOnTickColor,
[self.slider filledTrackTickColorForState:UIControlStateSelected]);
self.slider.enabled = YES;
self.slider.selected = NO;
self.slider.highlighted = YES;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOnTickColor,
[self.slider filledTrackTickColorForState:UIControlStateHighlighted]);
self.slider.enabled = YES;
self.slider.selected = YES;
self.slider.highlighted = YES;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOnTickColor,
[self.slider filledTrackTickColorForState:UIControlStateHighlighted |
UIControlStateSelected]);
}

#pragma mark - backgroundTrackTickColorForState

- (void)testBackgroundTrackTickColorForStateDefaults {
// Then
NSUInteger maximumStateValue = UIControlStateNormal | UIControlStateSelected |
UIControlStateHighlighted | UIControlStateDisabled;
for (NSUInteger state = 0; state <= maximumStateValue; ++state) {
XCTAssertEqualObjects([self.slider backgroundTrackTickColorForState:state],
UIColor.blackColor);
}
}

- (void)testBackgroundTrackTickColorForStateFallback {
// When
[self.slider setBackgroundTrackTickColor:UIColor.orangeColor forState:UIControlStateNormal];

// Then
NSUInteger maximumStateValue = UIControlStateNormal | UIControlStateSelected |
UIControlStateHighlighted | UIControlStateDisabled;
for (NSUInteger state = 0; state <= maximumStateValue; ++state) {
XCTAssertEqualObjects([self.slider backgroundTrackTickColorForState:state], UIColor.orangeColor,
@"(%@) is not equal to (%@) for state (%ld)",
[self.slider backgroundTrackTickColorForState:state], UIColor.orangeColor,
(long)state);
}
}

- (void)testBackgroundTrackTickColorForStateAppliesToThumbTrack {
// Given
self.slider.statefulAPIEnabled = YES;

// When
[self.slider setBackgroundTrackTickColor:UIColor.purpleColor forState:UIControlStateNormal];
[self.slider setBackgroundTrackTickColor:UIColor.redColor forState:UIControlStateHighlighted];
[self.slider setBackgroundTrackTickColor:UIColor.cyanColor forState:UIControlStateSelected];
[self.slider setBackgroundTrackTickColor:UIColor.grayColor forState:UIControlStateDisabled];
[self.slider setBackgroundTrackTickColor:UIColor.orangeColor
forState:UIControlStateHighlighted | UIControlStateSelected];
[self.slider setBackgroundTrackTickColor:UIColor.yellowColor
forState:UIControlStateDisabled | UIControlStateSelected];

// Then
self.slider.enabled = NO;
self.slider.selected = NO;
self.slider.highlighted = NO;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOffTickColor,
[self.slider backgroundTrackTickColorForState:UIControlStateDisabled]);
self.slider.enabled = NO;
self.slider.selected = YES;
self.slider.highlighted = NO;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOffTickColor,
[self.slider backgroundTrackTickColorForState:UIControlStateDisabled |
UIControlStateSelected]);
self.slider.enabled = NO;
self.slider.selected = NO;
self.slider.highlighted = YES;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOffTickColor,
[self.slider backgroundTrackTickColorForState:UIControlStateDisabled]);
self.slider.enabled = NO;
self.slider.selected = YES;
self.slider.highlighted = YES;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOffTickColor,
[self.slider backgroundTrackTickColorForState:UIControlStateDisabled |
UIControlStateSelected]);
self.slider.enabled = YES;
self.slider.selected = NO;
self.slider.highlighted = NO;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOffTickColor,
[self.slider backgroundTrackTickColorForState:UIControlStateNormal]);
self.slider.enabled = YES;
self.slider.selected = YES;
self.slider.highlighted = NO;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOffTickColor,
[self.slider backgroundTrackTickColorForState:UIControlStateSelected]);
self.slider.enabled = YES;
self.slider.selected = NO;
self.slider.highlighted = YES;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOffTickColor,
[self.slider backgroundTrackTickColorForState:UIControlStateHighlighted]);
self.slider.enabled = YES;
self.slider.selected = YES;
self.slider.highlighted = YES;
XCTAssertEqualObjects(self.slider.thumbTrack.trackOffTickColor,
[self.slider backgroundTrackTickColorForState:UIControlStateHighlighted |
UIControlStateSelected]);
}

#pragma mark - InkColor

- (void)testInkColorDefault {
Expand Down

0 comments on commit 3cbd588

Please sign in to comment.