You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

409 lines
12 KiB

//
// WXYZ_BadgeView.m
// WXReader
//
// Created by Andrew on 2020/8/7.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "WXYZ_BadgeView.h"
#import <QuartzCore/QuartzCore.h>
//%%% default diameter
CGFloat const RKNotificationHubDefaultDiameter = 30;
static CGFloat const kCountMagnitudeAdaptationRatio = 0.3;
//%%% pop values
static CGFloat const kPopStartRatio = .85;
static CGFloat const kPopOutRatio = 1.05;
static CGFloat const kPopInRatio = .95;
//%%% blink values
static CGFloat const kBlinkDuration = 0.1;
static CGFloat const kBlinkAlpha = 0.1;
//%%% bump values
static CGFloat const kFirstBumpDistance = 8.0;
static CGFloat const kBumpTimeSeconds = 0.13;
static CGFloat const SECOND_BUMP_DIST = 4.0;
static CGFloat const kBumpTimeSeconds2 = 0.1;
@interface RKView : UIView
@property (nonatomic) BOOL isUserChangingBackgroundColor;
@end
@implementation RKView
- (void)setBackgroundColor:(UIColor *)backgroundColor
{
if (self.isUserChangingBackgroundColor) {
super.backgroundColor = backgroundColor;
self.isUserChangingBackgroundColor = NO;
}
}
@end
@implementation WXYZ_BadgeView{
int curOrderMagnitude;
UILabel *countLabel;
RKView *redCircle;
CGPoint initialCenter;
CGRect baseFrame;
CGRect initialFrame;
BOOL isIndeterminateMode;
}
@synthesize hubView;
#pragma mark - SETUP
- (id)initWithView:(UIView *)view
{
self = [super init];
if (!self) return nil;
_maxCount = 100000;
[self setView:view andCount:0];
return self;
}
- (id)initWithBarButtonItem:(UIBarButtonItem *)barButtonItem
{
self = [self initWithView:[barButtonItem valueForKey:@"view"]];
[self scaleCircleSizeBy:0.7];
[self moveCircleByX:-5.0 Y:0];
return self;
}
//%%% give this a view and an initial count (0 hides the notification circle)
// and it will make a hub for you
- (void)setView:(UIView *)view andCount:(int)startCount
{
curOrderMagnitude = 0;
CGRect frame = view.frame;
isIndeterminateMode = NO;
redCircle = [[RKView alloc]init];
redCircle.userInteractionEnabled = NO;
redCircle.isUserChangingBackgroundColor = YES;
redCircle.backgroundColor = [UIColor redColor];
countLabel = [[UILabel alloc]initWithFrame:redCircle.frame];
countLabel.userInteractionEnabled = NO;
self.count = startCount;
[countLabel setTextAlignment:NSTextAlignmentCenter];
countLabel.textColor = [UIColor whiteColor];
countLabel.backgroundColor = [UIColor clearColor];
[self setCircleAtFrame:CGRectMake(frame.size.width- (RKNotificationHubDefaultDiameter*2/3), -RKNotificationHubDefaultDiameter/3, RKNotificationHubDefaultDiameter, RKNotificationHubDefaultDiameter)];
[view addSubview:redCircle];
[view addSubview:countLabel];
[view bringSubviewToFront:redCircle];
[view bringSubviewToFront:countLabel];
hubView = view;
[self checkZero];
}
//%%% set the frame of the notification circle relative to the button
- (void)setCircleAtFrame:(CGRect)frame
{
[redCircle setFrame:frame];
initialCenter = CGPointMake(frame.origin.x+frame.size.width/2, frame.origin.y+frame.size.height/2);
baseFrame = frame;
initialFrame = frame;
countLabel.frame = redCircle.frame;
redCircle.layer.cornerRadius = frame.size.height/2;
[countLabel setFont:[UIFont fontWithName:@"HelveticaNeue" size:frame.size.width/2]];
[self expandToFitLargerDigits];
}
//%%% moves the circle by x amount on the x axis and y amount on the y axis
- (void)moveCircleByX:(CGFloat)x Y:(CGFloat)y
{
CGRect frame = redCircle.frame;
frame.origin.x += x;
frame.origin.y += y;
[self setCircleAtFrame:frame];
}
//%%% changes the size of the circle. setting a scale of 1 has no effect
- (void)scaleCircleSizeBy:(CGFloat)scale
{
CGRect fr = initialFrame;
CGFloat width = fr.size.width * scale;
CGFloat height = fr.size.height * scale;
CGFloat wdiff = (fr.size.width - width) / 2;
CGFloat hdiff = (fr.size.height - height) / 2;
CGRect frame = CGRectMake(fr.origin.x + wdiff, fr.origin.y + hdiff, width, height);
[self setCircleAtFrame:frame];
}
//%%% change the color of the notification circle
- (void)setCircleColor:(UIColor*)circleColor labelColor:(UIColor*)labelColor
{
redCircle.isUserChangingBackgroundColor = YES;
redCircle.backgroundColor = circleColor;
[countLabel setTextColor:labelColor];
}
- (void)setCircleBorderColor:(UIColor *)color borderWidth:(CGFloat)width {
redCircle.layer.borderColor = color.CGColor;
redCircle.layer.borderWidth = width;
}
- (void)hideCount
{
countLabel.hidden = YES;
isIndeterminateMode = YES;
}
- (void)showCount
{
isIndeterminateMode = NO;
[self checkZero];
}
#pragma mark - ATTRIBUTES
//%%% increases count by 1
- (void)increment
{
[self incrementBy:1];
}
//%%% increases count by amount
- (void)incrementBy:(int)amount
{
self.count += amount;
}
//%%% decreases count
- (void)decrement
{
[self decrementBy:1];
}
//%%% decreases count by amount
- (void)decrementBy:(int)amount
{
if (amount >= self.count) {
self.count = 0;
return;
}
self.count -= amount;
}
//%%% set the count yourself
- (void)setCount:(int)newCount
{
_count = newCount;
NSString *labelText = [NSString stringWithFormat:@"%@", @(self.count)];
if (_count > self.maxCount){
labelText = [NSString stringWithFormat:@"%@+", @(self.maxCount)];
}
countLabel.text = labelText;
[self checkZero];
[self expandToFitLargerDigits];
}
//%% set the font of the label
- (void)setCountLabelFont:(UIFont *)font
{
[countLabel setFont:font];
}
- (UIFont *)countLabelFont
{
return countLabel.font;
}
#pragma mark - ANIMATION
//%%% animation that resembles facebook's pop
- (void)pop
{
const float height = baseFrame.size.height;
const float width = baseFrame.size.width;
const float pop_start_h = height * kPopStartRatio;
const float pop_start_w = width * kPopStartRatio;
const float time_start = 0.05;
const float pop_out_h = height * kPopOutRatio;
const float pop_out_w = width * kPopOutRatio;
const float time_out = .2;
const float pop_in_h = height * kPopInRatio;
const float pop_in_w = width * kPopInRatio;
const float time_in = .05;
const float pop_end_h = height;
const float pop_end_w = width;
const float time_end = 0.05;
CABasicAnimation *startSize = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
startSize.duration = time_start;
startSize.beginTime = 0;
startSize.fromValue = @(pop_end_h / 2);
startSize.toValue = @(pop_start_h / 2);
startSize.removedOnCompletion = FALSE;
CABasicAnimation *outSize = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
outSize.duration = time_out;
outSize.beginTime = time_start;
outSize.fromValue = startSize.toValue;
outSize.toValue = @(pop_out_h / 2);
outSize.removedOnCompletion = FALSE;
CABasicAnimation *inSize = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
inSize.duration = time_in;
inSize.beginTime = time_start+time_out;
inSize.fromValue = outSize.toValue;
inSize.toValue = @(pop_in_h / 2);
inSize.removedOnCompletion = FALSE;
CABasicAnimation *endSize = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
endSize.duration = time_end;
endSize.beginTime = time_in+time_out+time_start;
endSize.fromValue = inSize.toValue;
endSize.toValue = @(pop_end_h / 2);
endSize.removedOnCompletion = FALSE;
CAAnimationGroup *group = [CAAnimationGroup animation];
[group setDuration: time_start+time_out+time_in+time_end];
[group setAnimations:@[startSize, outSize, inSize, endSize]];
[redCircle.layer addAnimation:group forKey:nil];
[UIView animateWithDuration:time_start animations:^{
CGRect frame = redCircle.frame;
CGPoint center = redCircle.center;
frame.size.height = pop_start_h;
frame.size.width = pop_start_w;
redCircle.frame = frame;
redCircle.center = center;
}completion:^(BOOL complete){
[UIView animateWithDuration:time_out animations:^{
CGRect frame = redCircle.frame;
CGPoint center = redCircle.center;
frame.size.height = pop_out_h;
frame.size.width = pop_out_w;
redCircle.frame = frame;
redCircle.center = center;
}completion:^(BOOL complete){
[UIView animateWithDuration:time_in animations:^{
CGRect frame = redCircle.frame;
CGPoint center = redCircle.center;
frame.size.height = pop_in_h;
frame.size.width = pop_in_w;
redCircle.frame = frame;
redCircle.center = center;
}completion:^(BOOL complete){
[UIView animateWithDuration:time_end animations:^{
CGRect frame = redCircle.frame;
CGPoint center = redCircle.center;
frame.size.height = pop_end_h;
frame.size.width = pop_end_w;
redCircle.frame = frame;
redCircle.center = center;
}];
}];
}];
}];
}
//%%% animation that flashes on an off
- (void)blink
{
[self setAlpha:kBlinkAlpha];
[UIView animateWithDuration:kBlinkDuration animations:^{
[self setAlpha:1];
}completion:^(BOOL complete){
[UIView animateWithDuration:kBlinkDuration animations:^{
[self setAlpha:kBlinkAlpha];
}completion:^(BOOL complete){
[UIView animateWithDuration:kBlinkDuration animations:^{
[self setAlpha:1];
}];
}];
}];
}
//%%% animation that jumps similar to OSX dock icons
- (void)bump
{
if (!CGPointEqualToPoint(initialCenter,redCircle.center)) {
//%%% canel previous animation
}
[self bumpCenterY:0];
[UIView animateWithDuration:kBumpTimeSeconds animations:^{
[self bumpCenterY:kFirstBumpDistance];
}completion:^(BOOL complete){
[UIView animateWithDuration:kBumpTimeSeconds animations:^{
[self bumpCenterY:0];
}completion:^(BOOL complete){
[UIView animateWithDuration:kBumpTimeSeconds2 animations:^{
[self bumpCenterY:SECOND_BUMP_DIST];
}completion:^(BOOL complete){
[UIView animateWithDuration:kBumpTimeSeconds2 animations:^{
[self bumpCenterY:0];
}];
}];
}];
}];
}
#pragma mark - HELPERS
//%%% changes the Y origin of the notification circle
- (void)bumpCenterY:(float)yVal
{
CGPoint center = redCircle.center;
center.y = initialCenter.y-yVal;
redCircle.center = center;
countLabel.center = center;
}
- (void)setAlpha:(float)alpha
{
redCircle.alpha = alpha;
countLabel.alpha = alpha;
}
//%%% hides the notification if the value is 0
- (void)checkZero
{
if (self.count <= 0) {
redCircle.hidden = YES;
countLabel.hidden = YES;
} else {
redCircle.hidden = NO;
if (!isIndeterminateMode) {
countLabel.hidden = NO;
}
}
}
- (void)expandToFitLargerDigits {
int orderOfMagnitude = log10((double)self.count);
orderOfMagnitude = (orderOfMagnitude >= 2) ? orderOfMagnitude : 1;
CGRect frame = initialFrame;
frame.size.width = initialFrame.size.width * (1 + kCountMagnitudeAdaptationRatio * (orderOfMagnitude - 1));
frame.origin.x = initialFrame.origin.x - (frame.size.width - initialFrame.size.width) / 2;
[redCircle setFrame:frame];
initialCenter = CGPointMake(frame.origin.x+frame.size.width/2, frame.origin.y+frame.size.height/2);
baseFrame = frame;
countLabel.frame = redCircle.frame;
curOrderMagnitude = orderOfMagnitude;
}
@end