blob: 4729ddc569538bd843d02f7d9fe7e73a01175b39 [file] [log] [blame]
// Copyright 2016-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "MaterialColorScheme.h"
#import "MaterialPalettes.h"
#import "MaterialSlider+ColorThemer.h"
#import "MaterialSlider.h"
#import "MaterialTypographyScheme.h"
static NSString *const kReusableIdentifierItem = @"sliderItemCellIdentifier";
static CGFloat const kSliderHorizontalMargin = 16;
static CGFloat const kSliderVerticalMargin = 12;
static CGFloat const kSliderMinimumTouchSize = 48;
@interface MDCSliderModel : NSObject
@property(nonatomic, copy) NSString *labelString;
@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;
@property(nonatomic, assign) CGFloat anchorValue;
@property(nonatomic, assign) BOOL discreteValueLabel;
@property(nonatomic, assign) BOOL hollowCircle;
@property(nonatomic, assign) BOOL enabled;
@property(nonatomic, assign) BOOL hapticsEnabled;
@property(nonatomic, assign) BOOL shouldEnableHapticsForAllDiscreteValues;
- (void)didChangeMDCSliderValue:(MDCSlider *)slider;
@end
@implementation MDCSliderModel
- (instancetype)init {
if (self = [super init]) {
// Default values
_labelString = @"";
_labelColor = [UIColor blackColor];
_bgColor = [UIColor whiteColor];
_sliderColor = nil;
_trackBackgroundColor = nil;
_numDiscreteValues = 0;
_value = 0.5;
_anchorValue = -CGFLOAT_MAX;
_discreteValueLabel = YES;
_hollowCircle = YES;
_enabled = YES;
_hapticsEnabled = YES;
_shouldEnableHapticsForAllDiscreteValues = NO;
}
return self;
}
- (void)didChangeMDCSliderValue:(MDCSlider *)slider {
NSLog(@"did change %@ value: %f", NSStringFromClass([slider class]), slider.value);
_value = slider.value;
}
@end
@interface MDCSliderExampleCollectionViewCell : UICollectionViewCell
@property(nonatomic, strong, nullable) UIFont *labelFont;
- (void)applyModel:(MDCSliderModel *)model withColorScheme:(MDCSemanticColorScheme *)colorScheme;
@end
@implementation MDCSliderExampleCollectionViewCell {
UILabel *_label;
MDCSlider *_slider;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
_label = [[UILabel alloc] init];
[self.contentView addSubview:_label];
_slider = [[MDCSlider alloc] initWithFrame:CGRectZero];
[self.contentView addSubview:_slider];
}
return self;
}
- (void)applyModel:(MDCSliderModel *)model withColorScheme:(MDCSemanticColorScheme *)colorScheme {
_label.text = model.labelString;
_label.textColor = model.labelColor;
self.contentView.backgroundColor = model.bgColor;
_slider.statefulAPIEnabled = YES;
[MDCSliderColorThemer applySemanticColorScheme:colorScheme toSlider:_slider];
_slider.numberOfDiscreteValues = model.numDiscreteValues;
_slider.value = model.value;
_slider.filledTrackAnchorValue = model.anchorValue;
_slider.shouldDisplayDiscreteValueLabel = model.discreteValueLabel;
_slider.thumbHollowAtStart = model.hollowCircle;
_slider.enabled = model.enabled;
_slider.hapticsEnabled = model.hapticsEnabled;
_slider.shouldEnableHapticsForAllDiscreteValues = model.shouldEnableHapticsForAllDiscreteValues;
// Don't apply a `nil` color, use the default
if (model.sliderColor) {
[_slider setTrackFillColor:model.sliderColor forState:UIControlStateNormal];
[_slider setThumbColor:model.sliderColor forState:UIControlStateNormal];
_slider.inkColor = model.sliderColor;
}
if (model.trackBackgroundColor) {
[_slider setTrackBackgroundColor:model.trackBackgroundColor forState:UIControlStateNormal];
}
if (model.filledTickColor) {
[_slider setFilledTrackTickColor:model.filledTickColor forState:UIControlStateNormal];
}
if (model.backgroundTickColor) {
[_slider setBackgroundTrackTickColor:model.backgroundTickColor forState:UIControlStateNormal];
}
// Add target/action pair
[_slider addTarget:model
action:@selector(didChangeMDCSliderValue:)
forControlEvents:UIControlEventValueChanged];
}
- (void)prepareForReuse {
[super prepareForReuse];
// Remove target/action pairs
NSSet *targets = [_slider allTargets];
for (id target in targets) {
[_slider removeTarget:target action:NULL forControlEvents:UIControlEventValueChanged];
}
}
- (void)layoutSubviews {
[super layoutSubviews];
UIEdgeInsets safeArea = UIEdgeInsetsZero;
// Accommodate insets for iPhone X.
safeArea = self.safeAreaInsets;
safeArea.top = 0;
CGRect labelFrame =
CGRectMake(kSliderHorizontalMargin + 6, kSliderVerticalMargin,
self.contentView.frame.size.width - (2 * kSliderHorizontalMargin), 20);
_label.frame = UIEdgeInsetsInsetRect(labelFrame, safeArea);
CGSize sliderSize = [_slider intrinsicContentSize];
sliderSize.width = MAX(kSliderMinimumTouchSize, sliderSize.width);
sliderSize.height = MAX(kSliderMinimumTouchSize, sliderSize.height);
CGRect sliderFrame = CGRectMake(
kSliderHorizontalMargin,
self.contentView.frame.size.height - kSliderVerticalMargin - sliderSize.height,
self.contentView.frame.size.width - (2 * kSliderHorizontalMargin), sliderSize.height);
_slider.frame = UIEdgeInsetsInsetRect(sliderFrame, safeArea);
}
- (void)setLabelFont:(UIFont *)labelFont {
_label.font = labelFont;
}
- (UIFont *)labelFont {
return _label.font;
}
@end
@interface MDCSliderFlowLayout : UICollectionViewFlowLayout
@end
@implementation MDCSliderFlowLayout
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
if (!CGSizeEqualToSize(self.collectionView.bounds.size, newBounds.size)) {
[self invalidateLayout];
return YES;
}
return NO;
}
- (void)invalidateLayout {
[super invalidateLayout];
[self.collectionView setNeedsLayout];
}
- (CGSize)itemSize {
return CGSizeMake(self.collectionView.bounds.size.width, 100);
}
- (CGFloat)minimumInteritemSpacing {
return 0;
}
@end
@interface SliderCollectionViewController : UICollectionViewController
@property(nonatomic, strong) MDCSemanticColorScheme *colorScheme;
@end
@implementation SliderCollectionViewController {
NSMutableArray<MDCSliderModel *> *_sliders;
MDCTypographyScheme *_typographyScheme;
}
- (instancetype)init {
MDCSliderFlowLayout *layout = [[MDCSliderFlowLayout alloc] init];
if (self = [super initWithCollectionViewLayout:layout]) {
// Register cell class.
[self.collectionView registerClass:[MDCSliderExampleCollectionViewCell class]
forCellWithReuseIdentifier:kReusableIdentifierItem];
self.collectionView.alwaysBounceVertical = YES;
self.collectionView.backgroundColor = [UIColor whiteColor];
_typographyScheme = [[MDCTypographyScheme alloc] init];
// Init the sliders
_sliders = [[NSMutableArray alloc] init];
MDCSliderModel *model;
model = [[MDCSliderModel alloc] init];
model.labelString = @"Default slider";
model.value = (CGFloat)0.66;
[_sliders addObject:model];
model = [[MDCSliderModel alloc] init];
model.labelString = @"Green slider without hollow circle at 0";
model.sliderColor = MDCPalette.greenPalette.tint800;
model.hollowCircle = NO;
model.value = 0;
[_sliders addObject:model];
model = [[MDCSliderModel alloc] init];
model.labelString = @"Discrete slider with numeric value label";
model.numDiscreteValues = 5;
model.value = (CGFloat)0.2;
[_sliders addObject:model];
model = [[MDCSliderModel alloc] init];
model.labelString = @"Discrete slider without numeric value label";
model.numDiscreteValues = 7;
model.value = 1;
model.discreteValueLabel = NO;
[_sliders addObject:model];
model = [[MDCSliderModel alloc] init];
model.labelString = @"Discrete slider with full haptics";
model.numDiscreteValues = 5;
model.value = 1;
model.discreteValueLabel = NO;
model.shouldEnableHapticsForAllDiscreteValues = YES;
[_sliders addObject:model];
model = [[MDCSliderModel alloc] init];
model.labelString = @"Dark themed slider";
model.labelColor = [UIColor whiteColor];
model.trackBackgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:(CGFloat)0.3];
model.sliderColor = MDCPalette.bluePalette.tint500;
model.bgColor = [UIColor darkGrayColor];
model.value = (CGFloat)0.2;
[_sliders addObject:model];
model = [[MDCSliderModel alloc] init];
model.labelString = @"Anchored slider";
model.anchorValue = (CGFloat)0.5;
model.value = (CGFloat)0.7;
[_sliders addObject:model];
model = [[MDCSliderModel alloc] init];
model.labelString = @"Haptics Disabled Slider";
model.value = (CGFloat)0.66;
model.hapticsEnabled = NO;
[_sliders addObject:model];
model = [[MDCSliderModel alloc] init];
model.labelString = @"Disabled slider";
model.value = (CGFloat)0.5;
model.anchorValue = (CGFloat)0.1;
model.enabled = NO;
[_sliders addObject:model];
_colorScheme =
[[MDCSemanticColorScheme alloc] initWithDefaults:MDCColorSchemeDefaultsMaterial201804];
}
return self;
}
#pragma mark - <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
return [_sliders count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MDCSliderExampleCollectionViewCell *cell =
[collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem
forIndexPath:indexPath];
MDCSliderModel *model = [_sliders objectAtIndex:indexPath.item];
[cell applyModel:model withColorScheme:self.colorScheme];
cell.labelFont = _typographyScheme.subtitle2;
return cell;
}
@end
@implementation SliderCollectionViewController (CatalogByConvention)
+ (NSDictionary *)catalogMetadata {
return @{
@"breadcrumbs" : @[ @"Slider", @"Slider" ],
@"description" : @"Sliders allow users to make selections from a range of values.",
@"primaryDemo" : @YES,
@"presentable" : @YES,
};
}
@end