// 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 *_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 - - (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