246 lines
8.5 KiB
Objective-C

//
// TYMActivityIndicatorView.m
// TYMActivityIndicatorView
//
// Created by Yiming Tang on 2/9/14.
// Copyright (c) 2014 - 2016 Yiming Tang. All rights reserved.
//
#import "TYMActivityIndicatorView.h"
@interface TYMActivityIndicatorView ()
@property (nonatomic) BOOL animating;
@property (nonatomic) UIImageView *spinnerImageView;
@property (nonatomic) UIImageView *backgroundImageView;
@property (nonatomic) NSMutableDictionary *backgroundImageStorage;
@property (nonatomic) NSMutableDictionary *spinnerImageStorage;
@end
@implementation TYMActivityIndicatorView
#pragma mark - Accessors
@synthesize activityIndicatorViewStyle = _activityIndicatorViewStyle;
@synthesize animating = _animating;
@synthesize backgroundImageView = _backgroundImageView;
@synthesize backgroundImageStorage = _backgroundImageStorage;
@synthesize spinnerImageView = _spinnerImageView;
@synthesize spinnerImageStorage = _spinnerImageStorage;
@synthesize clockwise = _clockwise;
@synthesize hidesWhenStopped = _hidesWhenStopped;
@synthesize fullRotationDuration = _fullRotationDuration;
@synthesize minProgressUnit = _minProgressUnit;
@synthesize progress = _progress;
- (UIImageView *)backgroundImageView {
if (!_backgroundImageView) {
_backgroundImageView = [[UIImageView alloc] initWithFrame:self.bounds];
_backgroundImageView.translatesAutoresizingMaskIntoConstraints = NO;
}
return _backgroundImageView;
}
- (UIImageView *)spinnerImageView {
if (!_spinnerImageView) {
_spinnerImageView = [[UIImageView alloc] initWithFrame:self.bounds];
_spinnerImageView.translatesAutoresizingMaskIntoConstraints = NO;
}
return _spinnerImageView;
}
- (NSMutableDictionary *)backgroundImageStorage {
if (!_backgroundImageStorage) {
_backgroundImageStorage = [NSMutableDictionary dictionary];
}
return _backgroundImageStorage;
}
- (NSMutableDictionary *)spinnerImageStorage {
if (!_spinnerImageStorage) {
_spinnerImageStorage = [NSMutableDictionary dictionary];
}
return _spinnerImageStorage;
}
- (void)setBackgroundImage:(UIImage *)backgroundImage forActivityIndicatorStyle:(TYMActivityIndicatorViewStyle)style {
[self.backgroundImageStorage setObject:backgroundImage forKey:@(style)];
}
- (UIImage *)backgroundImageForActivityIndicatorStyle:(TYMActivityIndicatorViewStyle)style {
UIImage *backgroundImage = [self.backgroundImageStorage objectForKey:@(style)];
if (!backgroundImage) {
backgroundImage = [[[self class] appearance] backgroundImageForActivityIndicatorStyle:style];
}
return backgroundImage;
}
- (void)setSpinnerImage:(UIImage *)spinnerImage forActivityIndicatorStyle:(TYMActivityIndicatorViewStyle)style {
[self.spinnerImageStorage setObject:spinnerImage forKey:@(style)];
}
- (UIImage *)spinnerImageForActivityIndicatorStyle:(TYMActivityIndicatorViewStyle)style {
UIImage *spinnerImage = [self.spinnerImageStorage objectForKey:@(style)];
if (!spinnerImage) {
spinnerImage = [[[self class] appearance] spinnerImageForActivityIndicatorStyle:style];
}
return spinnerImage;
}
- (void)setActivityIndicatorViewStyle:(TYMActivityIndicatorViewStyle)activityIndicatorViewStyle {
_activityIndicatorViewStyle = activityIndicatorViewStyle;
self.backgroundImageView.image = [self backgroundImageForActivityIndicatorStyle:activityIndicatorViewStyle];
self.spinnerImageView.image = [self spinnerImageForActivityIndicatorStyle:activityIndicatorViewStyle];
}
- (BOOL)isAnimating {
return self.animating;
}
#pragma mark - Class Methods
+ (void)initialize {
if (self == [TYMActivityIndicatorView class]) {
TYMActivityIndicatorView *appearance = [TYMActivityIndicatorView appearance];
[appearance setClockwise:YES];
[appearance setHidesWhenStopped:NO];
[appearance setFullRotationDuration:1.0];
[appearance setMinProgressUnit:0.01f];
[appearance setBackgroundImage:[UIImage imageNamed:@"TYMActivityIndicatorView.bundle/background-normal"] forActivityIndicatorStyle:TYMActivityIndicatorViewStyleNormal];
[appearance setSpinnerImage:[UIImage imageNamed:@"TYMActivityIndicatorView.bundle/spinner-normal"] forActivityIndicatorStyle:TYMActivityIndicatorViewStyleNormal];
[appearance setBackgroundImage:[UIImage imageNamed:@"TYMActivityIndicatorView.bundle/background-small"] forActivityIndicatorStyle:TYMActivityIndicatorViewStyleSmall];
[appearance setSpinnerImage:[UIImage imageNamed:@"TYMActivityIndicatorView.bundle/spinner-small"] forActivityIndicatorStyle:TYMActivityIndicatorViewStyleSmall];
[appearance setBackgroundImage:[UIImage imageNamed:@"TYMActivityIndicatorView.bundle/background-large"] forActivityIndicatorStyle:TYMActivityIndicatorViewStyleLarge];
[appearance setSpinnerImage:[UIImage imageNamed:@"TYMActivityIndicatorView.bundle/spinner-large"] forActivityIndicatorStyle:TYMActivityIndicatorViewStyleLarge];
}
}
#pragma mark - UIView
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self initialize];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
[self initialize];
}
return self;
}
- (id)initWithActivityIndicatorStyle:(TYMActivityIndicatorViewStyle)style {
if ((self = [self initWithFrame:CGRectZero])) {
self.activityIndicatorViewStyle = style;
}
return self;
}
- (CGSize)intrinsicContentSize {
return [self sizeThatFits:CGSizeZero];
}
- (CGSize)sizeThatFits:(CGSize)size {
CGSize backgroundImageSize = self.backgroundImageView.image.size;
CGSize indicatorImageSize = self.spinnerImageView.image.size;
return CGSizeMake(fmaxf(backgroundImageSize.width, indicatorImageSize.width), fmaxf(backgroundImageSize.height, indicatorImageSize.height));
}
#pragma mark - Public
- (void)startAnimating {
if (self.animating) return;
self.animating = YES;
self.hidden = NO;
CGFloat toValue = self.clockwise ? M_PI * 2 : -M_PI * 2;
[self rotateView:self.spinnerImageView from:0.0 to:toValue duration:self.fullRotationDuration repeatCount:HUGE_VALF forKey:@"rotation" removeAnimationOnCompletion:NO];
}
- (void)stopAnimating {
if (!self.animating) return;
self.animating = NO;
[self.spinnerImageView.layer removeAnimationForKey:@"rotation"];
if (self.hidesWhenStopped) {
self.hidden = YES;
}
}
- (void)setProgress:(CGFloat)progress {
if (progress < 0.0 || progress > 1.0) return;
if (fabs(_progress - progress) < self.minProgressUnit) return;
CGFloat fromValue = M_PI * 2 * _progress;
CGFloat toValue = M_PI * 2 * progress;
if (!self.clockwise) {
fromValue = -fromValue;
toValue = -toValue;
}
[self rotateView:self.spinnerImageView from:fromValue to:toValue duration:0.15f repeatCount:0 forKey:@"rotation" removeAnimationOnCompletion:NO];
_progress = progress;
}
#pragma mark - Private
- (void)initialize {
self.userInteractionEnabled = NO;
[self addSubview:self.backgroundImageView];
[self addSubview:self.spinnerImageView];
[self setupViewConstraints];
}
- (void)setupViewConstraints {
NSDictionary *views = @{
@"backgroundImageView": self.backgroundImageView,
@"spinnerImageView": self.spinnerImageView
};
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[backgroundImageView]|" options:kNilOptions metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[backgroundImageView]|" options:kNilOptions metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[spinnerImageView]|" options:kNilOptions metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[spinnerImageView]|" options:kNilOptions metrics:nil views:views]];
}
- (void)rotateView:(UIView *)view from:(CGFloat)fromValue to:(CGFloat)toValue duration:(CGFloat)duration repeatCount:(CGFloat)repeatCount forKey:(NSString *)key removeAnimationOnCompletion:(BOOL)removeOnCompletion {
CABasicAnimation *rotationAnimation = [CABasicAnimation animation];
rotationAnimation.keyPath = @"transform.rotation.z";
rotationAnimation.fromValue = @(fromValue);
rotationAnimation.toValue = @(toValue);
rotationAnimation.duration = duration;
rotationAnimation.repeatCount = repeatCount;
rotationAnimation.removedOnCompletion = removeOnCompletion;
rotationAnimation.fillMode = kCAFillModeForwards;
[view.layer addAnimation:rotationAnimation forKey:key];
}
@end