From a806413bd6414f684899c9a0fae10c3fc8aa54fd Mon Sep 17 00:00:00 2001 From: Louis Romero Date: Sat, 9 Jul 2016 00:00:37 +0200 Subject: [PATCH] [ProgressView] Add the ProgressView component. Summary: Adds a ProgressView component. It displays determinate progress linearly. Note: the README.md and API has previously been reviewed. Feel free to leave comments, but you can also skip for this review and focus on the implementation. Test Plan: - Open Catalog > Progress View and verify looks. Reviewers: randallli, junius, iangordon, O1 Material components iOS, ajsecord Reviewed By: O1 Material components iOS, ajsecord Tags: #material_components_ios Differential Revision: http://codereview.cc/D1103 --- MaterialComponents.podspec | 8 + ROADMAP.md | 2 +- catalog/Podfile.lock | 5 +- components/ProgressView/.jazzy.yaml | 5 + components/ProgressView/README.md | 166 ++++++++++ .../docs/assets/progress_view.mp4 | Bin 0 -> 34038 bytes .../docs/assets/progress_view.png | Bin 0 -> 38021 bytes .../examples/ProgressViewExample.m | 239 +++++++++++++++ components/ProgressView/src/MDCProgressView.h | 73 +++++ components/ProgressView/src/MDCProgressView.m | 284 ++++++++++++++++++ .../ProgressView/src/MaterialProgressView.h | 17 ++ .../tests/unit/ProgressViewTests.m | 65 ++++ components/README.md | 4 + demos/Pesto/Podfile.lock | 5 +- demos/Shrine/Podfile.lock | 5 +- 15 files changed, 874 insertions(+), 4 deletions(-) create mode 100644 components/ProgressView/.jazzy.yaml create mode 100644 components/ProgressView/README.md create mode 100644 components/ProgressView/docs/assets/progress_view.mp4 create mode 100644 components/ProgressView/docs/assets/progress_view.png create mode 100644 components/ProgressView/examples/ProgressViewExample.m create mode 100644 components/ProgressView/src/MDCProgressView.h create mode 100644 components/ProgressView/src/MDCProgressView.m create mode 100644 components/ProgressView/src/MaterialProgressView.h create mode 100644 components/ProgressView/tests/unit/ProgressViewTests.m diff --git a/MaterialComponents.podspec b/MaterialComponents.podspec index 8dfdfeb6d15..6209d0f5e33 100644 --- a/MaterialComponents.podspec +++ b/MaterialComponents.podspec @@ -183,6 +183,14 @@ Pod::Spec.new do |s| ss.header_mappings_dir = "components/#{ss.base_name}/src" end + s.subspec "ProgressView" do |ss| + ss.public_header_files = "components/#{ss.base_name}/src/*.h" + ss.source_files = "components/#{ss.base_name}/src/*.{h,m}" + ss.header_mappings_dir = "components/#{ss.base_name}/src" + + ss.dependency "MaterialComponents/private/RTL" + end + s.subspec "RobotoFontLoader" do |ss| ss.public_header_files = "components/#{ss.base_name}/src/*.h" ss.source_files = "components/#{ss.base_name}/src/*.{h,m}", "components/#{ss.base_name}/src/private/*.{h,m}" diff --git a/ROADMAP.md b/ROADMAP.md index e269dae11b3..5aa0f578f24 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,6 +4,6 @@ components: - HUD - Textfields - ActivityIndicator -- ProgressBar +- ProgressView For suggestions, please [file a GitHub issue](https://github.com/google/material-components-ios/issues). diff --git a/catalog/Podfile.lock b/catalog/Podfile.lock index fef327f066c..fdd82ea17bd 100644 --- a/catalog/Podfile.lock +++ b/catalog/Podfile.lock @@ -17,6 +17,7 @@ PODS: - MaterialComponents/PageControl (= 12.0.1) - MaterialComponents/Palettes (= 12.0.1) - MaterialComponents/private (= 12.0.1) + - MaterialComponents/ProgressView (= 12.0.1) - MaterialComponents/RobotoFontLoader (= 12.0.1) - MaterialComponents/ShadowElevations (= 12.0.1) - MaterialComponents/ShadowLayer (= 12.0.1) @@ -113,6 +114,8 @@ PODS: - MaterialComponents/private/Color - MaterialComponents/ShadowElevations - MaterialComponents/ShadowLayer + - MaterialComponents/ProgressView (12.0.1): + - MaterialComponents/private/RTL - MaterialComponents/RobotoFontLoader (12.0.1): - MaterialComponents/FontDiskLoader - MaterialComponents/Typography @@ -152,7 +155,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: CatalogByConvention: 0137d280781667dd3d856a14cdf507f5ac8eedc0 - MaterialComponents: c9194c49be10b8ac59cb8f857bedecbb511705a8 + MaterialComponents: ed704822f99f7286a3e5d2d4cc7ffdeac044c211 MaterialComponentsCatalog: c2911e8ad9f380350107f124f88a43b9fe012d19 MaterialComponentsUnitTests: 03755d2a184c1d21a66c86c0e333d7b087e2f662 diff --git a/components/ProgressView/.jazzy.yaml b/components/ProgressView/.jazzy.yaml new file mode 100644 index 00000000000..35c09e16277 --- /dev/null +++ b/components/ProgressView/.jazzy.yaml @@ -0,0 +1,5 @@ +# Auto-generated by ./scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: ProgressView +umbrella_header: src/MaterialProgressView.h +objc: true +sdk: iphonesimulator diff --git a/components/ProgressView/README.md b/components/ProgressView/README.md new file mode 100644 index 00000000000..7443571392e --- /dev/null +++ b/components/ProgressView/README.md @@ -0,0 +1,166 @@ +--- +title: "Progress View" +layout: detail +section: components +excerpt: "Progress View is a determinate and linear progress indicator that implements Material Design animation and layout." +--- +# Progress View + +
+ + [![ScreenShot](docs/assets/progress_view.png)](docs/assets/progress_view.mp4) +
+ +This control is designed to be a drop-in replacement for `UIProgressView`, with a user experience +influenced by [Material Design specifications](https://material.google.com/components/progress-activity.html#) +for animation and layout. The API methods are the same as a `UIProgressView`, with the addition of a +few key methods required to achieve the desired animation of the control. + + +### Material Design Specifications + + + +### API Documentation + + + +- - - + +## Installation + +### Requirements + +- Xcode 7.0 or higher +- iOS SDK version 7.0 or higher + +### Installation with CocoaPods + +To add this component to your Xcode project using CocoaPods, add the following to your `Podfile`: + +~~~ bash +pod 'MaterialComponents/ProgressView' +~~~ + +Then, run the following command: + +~~~ bash +$ pod install +~~~ + +- - - + +## Differences From UIProgressView + +This progress view provides an animation effect when showing and hidding it: it grows up (resp. +shrinks down). Additionally, all animated changes APIs take an optional completion block, to +synchronize multistep animations. + +- - - + +## Usage + +### Importing + +Before using Progress View, you'll need to import it: + + +#### Objective-C + +~~~ objc +#import "MaterialProgressView.h" +~~~ + +#### Swift +~~~ swift +import MaterialComponents +~~~ + + +Add the progress view to your view hierarchy like you would with any other view. Note that it works +best when the progress view is added at the bottom of a view, as showing (resp. hiding) grows up +(resp. shrinks down). + +### Step 1: Add the progress view to a view + +Add the progress view to a view and set the desired progress and hidden state. + + +#### Objective-C + +~~~ objc +@implementation ViewController { + MDCProgressView *_progressView; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Progress view configuration. + _progressView = [[MDCProgressView alloc] initWithFrame:myFrame]; + _progressView.progress = 0; // You can also set a greater progress for actions already started. + [self.view addSubview:_progressView]; +} + +~~~ + +#### Swift + +~~~ swift +class ProgressViewSwiftExampleViewController: UIViewController { + + let progressView = MDCProgressView() + + override func viewDidLoad() { + super.viewDidLoad() + + progressView.progress = 0 + + let progressViewHeight = CGFloat(2) + progressView.frame = CGRectMake(0, view.bounds.height - progressViewHeight, view.bounds.width, progressViewHeight); + view.addSubview(progressView) + } + +~~~ + + +### Step 2: Change the progress and hidden state + +Both the progress and the hidden state can be animated, with a completion block. + + +#### Objective-C + +~~~ objc +- (void)startAndShowProgressView { + _progressView.progress = 0; + [_progressView setHidden:NO animated:YES completion:nil]; +} + +- (void)completeAndHideProgressView { + [_progressView setProgress:1 animated:YES completion:^(BOOL finished){ + [_progressView setHidden:YES animated:YES completion:nil]; + }]; +} +~~~ + +#### Swift + +~~~ swift +func startAndShowProgressView { + progressView.progress = 0 + progressView.setHidden(false, animated: true) +} + +func completeAndHideProgressView { + progressView.setProgress(1, animated: true) { BOOL finished in + progressView.setHidden(true, animated: true) + } +} +~~~ + + diff --git a/components/ProgressView/docs/assets/progress_view.mp4 b/components/ProgressView/docs/assets/progress_view.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..3f72c2aec00405330208eecfd53b5a4cc5f14aa9 GIT binary patch literal 34038 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 I?5wPR0f3nr)&Kwi literal 0 HcmV?d00001 diff --git a/components/ProgressView/docs/assets/progress_view.png b/components/ProgressView/docs/assets/progress_view.png new file mode 100644 index 0000000000000000000000000000000000000000..dcd8e3a8561f7fc136f02bf2e0ed0f01751fd320 GIT binary patch literal 38021 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 + +#import "MaterialPalettes.h" +#import "MaterialProgressView.h" +#import "MaterialTypography.h" + +static const CGFloat MDCProgressViewAnimationDuration = 1.f; + +@interface ProgressViewExample : UIViewController + +@property(nonatomic, strong) MDCProgressView *stockProgressView; +@property(nonatomic, strong) UILabel *stockProgressLabel; + +@property(nonatomic, strong) MDCProgressView *tintedProgressView; +@property(nonatomic, strong) UILabel *tintedProgressLabel; + +@property(nonatomic, strong) MDCProgressView *fullyColoredProgressView; +@property(nonatomic, strong) UILabel *fullyColoredProgressLabel; + +@property(nonatomic, strong) MDCProgressView *backwardProgressView; +@property(nonatomic, strong) UILabel *backwardProgressLabel; + +@end + +@implementation ProgressViewExample + +- (void)setupProgressViews { + _stockProgressView = [[MDCProgressView alloc] init]; + _stockProgressView.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:_stockProgressView]; + // Hide the progress view at setup time. + _stockProgressView.hidden = YES; + + _tintedProgressView = [[MDCProgressView alloc] init]; + _tintedProgressView.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:_tintedProgressView]; + _tintedProgressView.progressTintColor = [[MDCPalette redPalette] tint500]; + // Reset the track tint color to be based off of the progress tint color. + _tintedProgressView.trackTintColor = nil; + // Hide the progress view at setup time. + _tintedProgressView.hidden = YES; + + _fullyColoredProgressView = [[MDCProgressView alloc] init]; + _fullyColoredProgressView.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:_fullyColoredProgressView]; + _fullyColoredProgressView.progressTintColor = [[MDCPalette greenPalette] tint500]; + _fullyColoredProgressView.trackTintColor = [[MDCPalette yellowPalette] tint500]; + // Hide the progress view at setup time. + _fullyColoredProgressView.hidden = YES; + + _backwardProgressView = [[MDCProgressView alloc] init]; + _backwardProgressView.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:_backwardProgressView]; + // Have a non-zero progress at setup time. + _backwardProgressView.progress = 0.33; +} + +@end + +@implementation ProgressViewExample (Supplemental) + +- (void)dealloc { + [NSObject cancelPreviousPerformRequestsWithTarget:self]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.title = @"Progress View"; + self.view.backgroundColor = [UIColor whiteColor]; + + [self setupProgressViews]; + [self setupLabels]; + [self setupConstraints]; +} + +- (void)setupLabels { + _stockProgressLabel = [[UILabel alloc] init]; + _stockProgressLabel.text = @"Progress"; + _stockProgressLabel.font = [MDCTypography captionFont]; + _stockProgressLabel.alpha = [MDCTypography captionFontOpacity]; + _stockProgressLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:_stockProgressLabel]; + + _tintedProgressLabel = [[UILabel alloc] init]; + _tintedProgressLabel.text = @"Progress with progress tint"; + _tintedProgressLabel.font = [MDCTypography captionFont]; + _tintedProgressLabel.alpha = [MDCTypography captionFontOpacity]; + _tintedProgressLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:_tintedProgressLabel]; + + _fullyColoredProgressLabel = [[UILabel alloc] init]; + _fullyColoredProgressLabel.text = @"Progress with custom colors"; + _fullyColoredProgressLabel.font = [MDCTypography captionFont]; + _fullyColoredProgressLabel.alpha = [MDCTypography captionFontOpacity]; + _fullyColoredProgressLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:_fullyColoredProgressLabel]; + + _backwardProgressLabel = [[UILabel alloc] init]; + _backwardProgressLabel.text = @"Backward progress"; + _backwardProgressLabel.font = [MDCTypography captionFont]; + _backwardProgressLabel.alpha = [MDCTypography captionFontOpacity]; + _backwardProgressLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:_backwardProgressLabel]; +} + +- (void)setupConstraints { + NSArray *views = @{ + @"stockView" : _stockProgressView, + @"stockLabel" : _stockProgressLabel, + @"tintedView" : _tintedProgressView, + @"tintedLabel" : _tintedProgressLabel, + @"coloredView" : _fullyColoredProgressView, + @"coloredLabel" : _fullyColoredProgressLabel, + @"backwardView" : _backwardProgressView, + @"backwardLabel" : _backwardProgressLabel, + }; + NSArray *metrics = @{ + @"p" : @20, + @"s" : @40, + @"h" : @2, + }; + + NSArray *verticalConstraints = + [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(p)-" + "[stockView(==h)]-(p)-[stockLabel]-(s)-" + "[tintedView(==h)]-(p)-[tintedLabel]-(s)-" + "[coloredView(==h)]-(p)-[coloredLabel]-(s)-" + "[backwardView(==h)]-(p)-[backwardLabel]" + options:0 + metrics:metrics + views:views]; + [self.view addConstraints:verticalConstraints]; + + NSMutableArray *horizontalConstraints = [NSMutableArray array]; + NSArray *horizontalVisualFormats = @[ + @"H:|-(p)-[stockView]-(p)-|", + @"H:|-(p)-[tintedView]-(p)-|", + @"H:|-(p)-[coloredView]-(p)-|", + @"H:|-(p)-[backwardView]-(p)-|", + @"H:|-(>=p)-[stockLabel]-(>=p)-|", + @"H:|-(>=p)-[tintedLabel]-(>=p)-|", + @"H:|-(>=p)-[coloredLabel]-(>=p)-|", + @"H:|-(>=p)-[backwardLabel]-(>=p)-|", + ]; + for (NSArray *format in horizontalVisualFormats) { + [horizontalConstraints + addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:format + options:0 + metrics:metrics + views:views]]; + } + [self.view addConstraints:horizontalConstraints]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + [self animateStep1:_stockProgressView]; + [self animateStep1:_tintedProgressView]; + [self animateStep1:_fullyColoredProgressView]; + [self animateBackwardProgressView]; +} + +- (void)animateStep1:(MDCProgressView *)progressView { + progressView.progress = 0; + [progressView setHidden:NO + animated:YES + completion:^(BOOL finished) { + [self performSelector:@selector(animateStep2:) + withObject:progressView + afterDelay:MDCProgressViewAnimationDuration]; + }]; +} + +- (void)animateStep2:(MDCProgressView *)progressView { + [progressView setProgress:0.5 animated:YES completion:nil]; + [self performSelector:@selector(animateStep3:) + withObject:progressView + afterDelay:MDCProgressViewAnimationDuration]; +} + +- (void)animateStep3:(MDCProgressView *)progressView { + [progressView setProgress:1 animated:YES completion:nil]; + [self performSelector:@selector(animateStep4:) + withObject:progressView + afterDelay:MDCProgressViewAnimationDuration]; +} + +- (void)animateStep4:(MDCProgressView *)progressView { + [progressView setHidden:YES + animated:YES + completion:^(BOOL finished) { + [self performSelector:@selector(animateStep1:) + withObject:progressView + afterDelay:MDCProgressViewAnimationDuration]; + }]; +} + +- (void)animateBackwardProgressView { + [_backwardProgressView setProgress:1 - _backwardProgressView.progress + animated:YES + completion:^(BOOL finished) { + [self performSelector:@selector(animateBackwardProgressView) + withObject:nil + afterDelay:MDCProgressViewAnimationDuration]; + }]; +} + +#pragma mark - Catalog by Convention + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Progress View", @"Progress View" ]; +} + ++ (NSString *)catalogDescription { + return @"Progress View is a visual indication of an app loading content. It can display how " + @"long an operation will take."; +} + ++ (BOOL)catalogIsPrimaryDemo { + return YES; +} + +@end diff --git a/components/ProgressView/src/MDCProgressView.h b/components/ProgressView/src/MDCProgressView.h new file mode 100644 index 00000000000..fb57434a4a5 --- /dev/null +++ b/components/ProgressView/src/MDCProgressView.h @@ -0,0 +1,73 @@ +/* + Copyright 2016-present Google Inc. 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 + +/** + A Material linear determinate progress view. + + See https://www.google.com/design/spec/components/progress-activity.html + */ +IB_DESIGNABLE +@interface MDCProgressView : UIView + +/** + The color shown for the portion of the progress view that is filled. + + The default is a blue color. When changed, the trackTintColor is reset. + */ +@property(nonatomic, strong, null_resettable) UIColor *progressTintColor UI_APPEARANCE_SELECTOR; + +/** + The color shown for the portion of the progress view that is not filled. + + The default is a light version of the current progressTintColor. + */ +@property(nonatomic, strong, null_resettable) UIColor *trackTintColor UI_APPEARANCE_SELECTOR; + +/** + The current progress. + + The current progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where + 1.0 indicates the completion of the task. The default value is 0.0. Values less than 0.0 and + greater than 1.0 are pinned to those limits. + To animate progress changes, use -setProgress:animated:completion:. + */ +@property(nonatomic, assign) float progress; + +/** + Adjusts the current progress, optionally animating the change. + + @param progress The progress to set. + @param animated Whether the change should be animated. + @param completion The completion block executed at the end of the animation. + */ +- (void)setProgress:(float)progress + animated:(BOOL)animated + completion:(void (^__nullable)(BOOL finished))completion; + +/** + Changes the hidden state, optionally animating the change. + + @param hidden The hidden state to set. + @param animated Whether the change should be animated. + @param completion The completion block executed at the end of the animation. + */ +- (void)setHidden:(BOOL)hidden + animated:(BOOL)animated + completion:(void (^__nullable)(BOOL finished))completion; + +@end diff --git a/components/ProgressView/src/MDCProgressView.m b/components/ProgressView/src/MDCProgressView.m new file mode 100644 index 00000000000..eede04d796c --- /dev/null +++ b/components/ProgressView/src/MDCProgressView.m @@ -0,0 +1,284 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "MDCProgressView.h" + +#import + +#import "MaterialRTL.h" + +// Blue 500 from http://www.google.com/design/spec/style/color.html#color-color-palette . +static const uint32_t MDCProgressViewDefaultTintColor = 0x2196F3; + +// The ratio by which to desaturate the progress tint color to obtain the default track tint color. +static const CGFloat MDCProgressViewTrackColorDesaturation = 0.3f; + +// Creates a UIColor from a 24-bit RGB color encoded as an integer. +static inline UIColor *MDCColorFromRGB(uint32_t rgbValue) { + return [UIColor colorWithRed:((CGFloat)((rgbValue & 0xFF0000) >> 16)) / 255 + green:((CGFloat)((rgbValue & 0x00FF00) >> 8)) / 255 + blue:((CGFloat)((rgbValue & 0x0000FF) >> 0)) / 255 + alpha:1]; +} + +static const NSTimeInterval MDCProgressViewAnimationDuration = 0.25; + +@interface MDCProgressView () +@property(nonatomic, strong) UIView *progressView; +@property(nonatomic, strong) UIView *trackView; +@property(nonatomic) BOOL animatingHide; +// A UIProgressView to return the same format for the accessibility value. For example, when +// progress is 0.497, it reports "fifty per cent". +@property(nonatomic, strong) UIProgressView *accessibilityProgressView; +@end + +@implementation MDCProgressView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self commonMDCProgressViewInit]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + [self commonMDCProgressViewInit]; + } + return self; +} + +- (void)commonMDCProgressViewInit { + self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; + self.backgroundColor = [UIColor clearColor]; + self.clipsToBounds = YES; + self.isAccessibilityElement = YES; + + _trackView = [[UIView alloc] initWithFrame:self.frame]; + _trackView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + [self addSubview:_trackView]; + + _progressView = [[UIView alloc] initWithFrame:CGRectZero]; + [self addSubview:_progressView]; + + _progressView.backgroundColor = [[self class] defaultProgressTintColor]; + _trackView.backgroundColor = + [[self class] defaultTrackTintColorForProgressTintColor:_progressView.backgroundColor]; +} + +- (void)willMoveToSuperview:(UIView *)superview { + [super willMoveToSuperview:superview]; + [NSObject cancelPreviousPerformRequestsWithTarget:self]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + // Don't update the views when the hide animation is in progress. + if (!self.animatingHide) { + [self updateProgressView]; + [self updateTrackView]; + } +} + +- (UIColor *)progressTintColor { + return self.progressView.backgroundColor; +} + +- (void)setProgressTintColor:(UIColor *)progressTintColor { + if (progressTintColor == nil) { + progressTintColor = [[self class] defaultProgressTintColor]; + } + self.progressView.backgroundColor = progressTintColor; +} + +- (UIColor *)trackTintColor { + return self.trackView.backgroundColor; +} + +- (void)setTrackTintColor:(UIColor *)trackTintColor { + if (trackTintColor == nil) { + trackTintColor = + [[self class] defaultTrackTintColorForProgressTintColor:self.progressTintColor]; + } + self.trackView.backgroundColor = trackTintColor; +} + +- (void)setProgress:(float)progress { + if (progress > 1) + progress = 1; + if (progress < 0) + progress = 0; + _progress = progress; + [self accessibilityValueDidChange]; + [self setNeedsLayout]; +} + +- (void)setProgress:(float)progress + animated:(BOOL)animated + completion:(void (^__nullable)(BOOL finished))completion { + // Backward progress should not be animated between the old and new progress. Instead, reset to 0 + // without animation before animating to the new position. + if (progress < self.progress) { + self.progress = 0; + [self updateProgressView]; + } + self.progress = progress; + [UIView animateWithDuration:animated ? [[self class] animationDuration] : 0 + delay:0 + options:[[self class] animationOptions] + animations:^{ + [self updateProgressView]; + } + completion:completion]; +} + +- (void)setHidden:(BOOL)hidden { + [super setHidden:hidden]; + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, hidden ? nil : self); +} + +- (void)setHidden:(BOOL)hidden + animated:(BOOL)animated + completion:(void (^__nullable)(BOOL finished))completion { + if (hidden == self.hidden) + return; + + void (^animations)(void); + + if (hidden) { + self.animatingHide = YES; + animations = ^{ + CGFloat y = CGRectGetHeight(self.bounds); + + CGRect trackViewFrame = self.trackView.frame; + trackViewFrame.origin.y = y; + trackViewFrame.size.height = 0; + self.trackView.frame = trackViewFrame; + + CGRect progressViewFrame = self.progressView.frame; + progressViewFrame.origin.y = y; + progressViewFrame.size.height = 0; + self.progressView.frame = progressViewFrame; + }; + } else { + self.hidden = NO; + animations = ^{ + self.trackView.frame = self.bounds; + + CGRect progressViewFrame = self.progressView.frame; + progressViewFrame.origin.y = 0; + progressViewFrame.size.height = CGRectGetHeight(self.bounds); + self.progressView.frame = progressViewFrame; + }; + } + + [UIView animateWithDuration:animated ? [[self class] animationDuration] : 0 + delay:0 + options:[[self class] animationOptions] + animations:animations + completion:^(BOOL finished) { + if (hidden) { + self.animatingHide = NO; + self.hidden = YES; + } + if (completion) + completion(finished); + }]; +} + +#pragma mark Accessibility + +- (UIProgressView *)accessibilityProgressView { + if (!_accessibilityProgressView) + _accessibilityProgressView = [[UIProgressView alloc] init]; + return _accessibilityProgressView; +} + +- (NSString *)accessibilityValue { + self.accessibilityProgressView.progress = self.progress; + return self.accessibilityProgressView.accessibilityValue; +} + +- (void)accessibilityValueDidChange { + // Store a strong reference to self until the end of the method. Indeed, + // a previous -performSelector:withObject:afterDelay: might be the last thing + // to retain self, so calling +cancelPreviousPerformRequestsWithTarget: might + // deallocate self. + MDCProgressView *strongSelf = self; + // Cancel unprocessed announcements and replace them with the most up-to-date + // value. That way, they don't overlap and don't spam the user. + [NSObject cancelPreviousPerformRequestsWithTarget:strongSelf + selector:@selector(announceAccessibilityValueChange) + object:nil]; + // Schedule a new announcement. + [strongSelf performSelector:@selector(announceAccessibilityValueChange) + withObject:nil + afterDelay:1]; +} + +- (void)announceAccessibilityValueChange { + if ([self accessibilityElementIsFocused]) { + UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, + [self accessibilityValue]); + } +} + +#pragma mark Private + ++ (NSTimeInterval)animationDuration { + return MDCProgressViewAnimationDuration; +} + ++ (UIViewAnimationOptions)animationOptions { + // Since the animation is fake, using a linear interpolation avoids the speeding up and slowing + // down that repeated easing in and out causes. + return UIViewAnimationOptionCurveLinear; +} + ++ (UIColor *)defaultProgressTintColor { + return MDCColorFromRGB(MDCProgressViewDefaultTintColor); +} + ++ (UIColor *)defaultTrackTintColorForProgressTintColor:(UIColor *)progressTintColor { + CGFloat hue, saturation, brightness, alpha; + if ([progressTintColor getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha]) { + CGFloat newSaturation = MIN(saturation * MDCProgressViewTrackColorDesaturation, 1.0f); + return [UIColor colorWithHue:hue saturation:newSaturation brightness:brightness alpha:alpha]; + } + return [UIColor clearColor]; +} + +- (void)updateProgressView { + // Update progressView with the current progress value. + CGFloat progressWidth = ceil(self.progress * CGRectGetWidth(self.bounds)); + CGRect progressFrame = CGRectMake(0, 0, progressWidth, CGRectGetHeight(self.bounds)); + self.progressView.frame = MDCRectFlippedForRTL(progressFrame, CGRectGetWidth(self.bounds), + self.mdc_effectiveUserInterfaceLayoutDirection); +} + +- (void)updateTrackView { + const CGSize size = self.bounds.size; + self.trackView.frame = self.hidden ? CGRectMake(0.0, size.height, size.width, 0.0) : self.bounds; +} + +@end diff --git a/components/ProgressView/src/MaterialProgressView.h b/components/ProgressView/src/MaterialProgressView.h new file mode 100644 index 00000000000..9a1f832aafc --- /dev/null +++ b/components/ProgressView/src/MaterialProgressView.h @@ -0,0 +1,17 @@ +/* + Copyright 2016-present Google Inc. 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 "MDCProgressView.h" diff --git a/components/ProgressView/tests/unit/ProgressViewTests.m b/components/ProgressView/tests/unit/ProgressViewTests.m new file mode 100644 index 00000000000..c146b24bd9c --- /dev/null +++ b/components/ProgressView/tests/unit/ProgressViewTests.m @@ -0,0 +1,65 @@ +/* + Copyright 2016-present Google Inc. 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 + +#import "MaterialProgressView.h" + +@interface ProgressViewTests : XCTestCase + +@end + +@implementation ProgressViewTests { + MDCProgressView *_progressView; +} + +- (void)setUp { + [super setUp]; + _progressView = [[MDCProgressView alloc] initWithFrame:CGRectZero]; +} + +- (void)tearDown { + _progressView = nil; + [super tearDown]; +} + +#pragma mark - Tests + +- (void)testInitialProgress { + XCTAssertEqual(_progressView.progress, 0); +} + +- (void)testSetProgress { + _progressView.progress = 0.1234f; + XCTAssertEqual(_progressView.progress, 0.1234f); +} + +- (void)testSetProgressAnimated { + [_progressView setProgress:0.777f animated:YES completion:nil]; + XCTAssertEqual(_progressView.progress, 0.777f); +} + +- (void)testProgressClampedAt0 { + _progressView.progress = -1.f; + XCTAssertEqual(_progressView.progress, 0.f); +} + +- (void)testProgressClampedAt1 { + _progressView.progress = 2.f; + XCTAssertEqual(_progressView.progress, 1.f); +} + +@end diff --git a/components/README.md b/components/README.md index 47b681fd051..631864100bb 100644 --- a/components/README.md +++ b/components/README.md @@ -94,6 +94,10 @@ and are developed by a team of iOS engineers and UX designers at Google. ](Palettes/) +- [**ProgressView** + A determinate and linear progress indicator that implements Material Design animation and layout. + ](ProgressView/) + - [**ShadowElevations** Provides the most commonly used elevations specified in Material Design. ](ShadowElevations/) diff --git a/demos/Pesto/Podfile.lock b/demos/Pesto/Podfile.lock index cd50adfe453..b08934845e5 100644 --- a/demos/Pesto/Podfile.lock +++ b/demos/Pesto/Podfile.lock @@ -16,6 +16,7 @@ PODS: - MaterialComponents/PageControl (= 12.0.1) - MaterialComponents/Palettes (= 12.0.1) - MaterialComponents/private (= 12.0.1) + - MaterialComponents/ProgressView (= 12.0.1) - MaterialComponents/RobotoFontLoader (= 12.0.1) - MaterialComponents/ShadowElevations (= 12.0.1) - MaterialComponents/ShadowLayer (= 12.0.1) @@ -112,6 +113,8 @@ PODS: - MaterialComponents/private/Color - MaterialComponents/ShadowElevations - MaterialComponents/ShadowLayer + - MaterialComponents/ProgressView (12.0.1): + - MaterialComponents/private/RTL - MaterialComponents/RobotoFontLoader (12.0.1): - MaterialComponents/FontDiskLoader - MaterialComponents/Typography @@ -137,7 +140,7 @@ EXTERNAL SOURCES: :path: ../../ SPEC CHECKSUMS: - MaterialComponents: c9194c49be10b8ac59cb8f857bedecbb511705a8 + MaterialComponents: ed704822f99f7286a3e5d2d4cc7ffdeac044c211 PODFILE CHECKSUM: f138be16d4835113ff672258fc7529fad3f90e91 diff --git a/demos/Shrine/Podfile.lock b/demos/Shrine/Podfile.lock index e302c154727..df2d42e39ee 100644 --- a/demos/Shrine/Podfile.lock +++ b/demos/Shrine/Podfile.lock @@ -16,6 +16,7 @@ PODS: - MaterialComponents/PageControl (= 12.0.1) - MaterialComponents/Palettes (= 12.0.1) - MaterialComponents/private (= 12.0.1) + - MaterialComponents/ProgressView (= 12.0.1) - MaterialComponents/RobotoFontLoader (= 12.0.1) - MaterialComponents/ShadowElevations (= 12.0.1) - MaterialComponents/ShadowLayer (= 12.0.1) @@ -112,6 +113,8 @@ PODS: - MaterialComponents/private/Color - MaterialComponents/ShadowElevations - MaterialComponents/ShadowLayer + - MaterialComponents/ProgressView (12.0.1): + - MaterialComponents/private/RTL - MaterialComponents/RobotoFontLoader (12.0.1): - MaterialComponents/FontDiskLoader - MaterialComponents/Typography @@ -137,7 +140,7 @@ EXTERNAL SOURCES: :path: ../../ SPEC CHECKSUMS: - MaterialComponents: c9194c49be10b8ac59cb8f857bedecbb511705a8 + MaterialComponents: ed704822f99f7286a3e5d2d4cc7ffdeac044c211 PODFILE CHECKSUM: b585ca32a2884e050823cc1f861e8b7246f7dcc1