mirror of
https://github.com/zhigang1992/MessageBarManager.git
synced 2026-01-12 22:48:46 +08:00
404 lines
13 KiB
Objective-C
404 lines
13 KiB
Objective-C
//
|
|
// MessageBarManager.m
|
|
//
|
|
// Created by Terry Worona on 5/13/13.
|
|
// Copyright (c) 2013 Terry Worona. All rights reserved.
|
|
//
|
|
|
|
#import "MessageBarManager.h"
|
|
|
|
// Quartz
|
|
#import <QuartzCore/QuartzCore.h>
|
|
|
|
// Delegate
|
|
#import "AppDelegate.h"
|
|
|
|
// Constants
|
|
#import "UIConstants.h"
|
|
|
|
#define kMessageBarAlpha 0.96
|
|
#define kMessageBarPadding 10
|
|
#define kMessageBarMaxDescriptionHeight 250
|
|
#define kMessageBarIconSize 36
|
|
#define kMessageBarDisplayDelay 3.0
|
|
#define kMessageBarTextOffset 2.0
|
|
#define kMessageBarAnimationDuration 0.25
|
|
|
|
@class MessageView;
|
|
|
|
@interface MessageView : UIView
|
|
|
|
@property (nonatomic, strong) NSString *titleString;
|
|
@property (nonatomic, strong) NSString *descriptionString;
|
|
@property (nonatomic, assign) MessageBarMessageType messageType;
|
|
@property (nonatomic, strong) UIImageView *shadowView;
|
|
|
|
@property (nonatomic, assign) BOOL hasCallback;
|
|
@property (nonatomic, strong) NSArray *callbacks;
|
|
|
|
@property (nonatomic, assign, getter = isHit) BOOL hit;
|
|
|
|
@property (nonatomic, assign, readonly) CGFloat height;
|
|
@property (nonatomic, assign, readonly) CGFloat width;
|
|
|
|
@property (nonatomic, assign) CGFloat duration;
|
|
|
|
- (id)initWithTitle:(NSString*)title description:(NSString*)description type:(MessageBarMessageType)type;
|
|
|
|
@end
|
|
|
|
@interface MessageBarManager ()
|
|
|
|
@property (nonatomic, strong) NSMutableArray *messageBarQueue;
|
|
@property (nonatomic, assign, getter = isMessageVisible) BOOL messageVisible;
|
|
@property (nonatomic, assign) CGFloat messageBarOffset;
|
|
|
|
+ (CGFloat)durationForMessageType:(MessageBarMessageType)messageType;
|
|
|
|
- (void)showNextMessage;
|
|
- (void)itemSelected:(UITapGestureRecognizer*)recognizer;
|
|
|
|
@end
|
|
|
|
@implementation MessageBarManager
|
|
|
|
@synthesize messageBarQueue = _messageBarQueue;
|
|
@synthesize messageVisible = _messageVisible;
|
|
@synthesize messageBarOffset = _messageBarOffset;
|
|
|
|
#pragma mark - Singleton
|
|
|
|
+ (MessageBarManager *)sharedInstance
|
|
{
|
|
static dispatch_once_t pred;
|
|
static MessageBarManager *instance = nil;
|
|
dispatch_once(&pred, ^{ instance = [[self alloc] init]; });
|
|
return instance;
|
|
}
|
|
|
|
#pragma mark - Static
|
|
|
|
+ (CGFloat)durationForMessageType:(MessageBarMessageType)messageType
|
|
{
|
|
return kMessageBarDisplayDelay;
|
|
}
|
|
|
|
#pragma mark - Alloc/Init
|
|
|
|
-(id)init
|
|
{
|
|
if(self = [super init]) {
|
|
_messageBarQueue = [[NSMutableArray alloc] init];
|
|
_messageVisible = NO;
|
|
_messageBarOffset = [[UIApplication sharedApplication] statusBarFrame].size.height;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - Public
|
|
|
|
- (void)showMessageWithTitle:(NSString*)title description:(NSString*)description type:(MessageBarMessageType)type
|
|
{
|
|
[self showMessageWithTitle:title description:description type:type forDuration:[MessageBarManager durationForMessageType:type] callback:nil];
|
|
}
|
|
|
|
- (void)showMessageWithTitle:(NSString*)title description:(NSString*)description type:(MessageBarMessageType)type callback:(void (^)())callback
|
|
{
|
|
[self showMessageWithTitle:title description:description type:type forDuration:[MessageBarManager durationForMessageType:type] callback:callback];
|
|
}
|
|
|
|
- (void)showMessageWithTitle:(NSString*)title description:(NSString*)description type:(MessageBarMessageType)type forDuration:(CGFloat)duration
|
|
{
|
|
[self showMessageWithTitle:title description:description type:type forDuration:duration callback:nil];
|
|
}
|
|
|
|
- (void)showMessageWithTitle:(NSString*)title description:(NSString*)description type:(MessageBarMessageType)type forDuration:(CGFloat)duration callback:(void (^)())callback
|
|
{
|
|
MessageView *messageView = [[MessageView alloc] initWithTitle:title description:description type:type];
|
|
|
|
messageView.callbacks = callback ? [NSArray arrayWithObject:callback] : [NSArray array];
|
|
messageView.hasCallback = callback ? YES : NO;
|
|
|
|
messageView.duration = duration;
|
|
messageView.hidden = YES;
|
|
|
|
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
|
|
[delegate.window insertSubview:messageView atIndex:1];
|
|
[_messageBarQueue addObject:messageView];
|
|
|
|
if (!_messageVisible){
|
|
[self showNextMessage];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Private
|
|
|
|
- (void)showNextMessage
|
|
{
|
|
if ([_messageBarQueue count] > 0){
|
|
_messageVisible = YES;
|
|
|
|
MessageView *messageView = [_messageBarQueue objectAtIndex:0];
|
|
messageView.frame = CGRectMake(0, -[messageView height], [messageView width], [messageView height]);
|
|
messageView.hidden = NO;
|
|
[messageView setNeedsDisplay];
|
|
|
|
UITapGestureRecognizer *gest = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(itemSelected:)];
|
|
[messageView addGestureRecognizer:gest];
|
|
|
|
if (messageView){
|
|
[_messageBarQueue removeObject:messageView];
|
|
|
|
[UIView animateWithDuration:kMessageBarAnimationDuration animations:^{
|
|
[messageView setFrame:CGRectMake(messageView.frame.origin.x, _messageBarOffset + messageView.frame.origin.y + [messageView height], [messageView width], [messageView height])]; // slide down
|
|
}];
|
|
|
|
[self performSelector:@selector(itemSelected:) withObject:messageView afterDelay:messageView.duration];
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - Gestures
|
|
|
|
- (void)itemSelected:(id)sender
|
|
{
|
|
MessageView *messageView = nil;
|
|
BOOL itemHit = NO;
|
|
if ([sender isKindOfClass:[UIGestureRecognizer class]]){
|
|
messageView = (MessageView*)((UIGestureRecognizer*)sender).view;
|
|
itemHit = YES;
|
|
}
|
|
else if ([sender isKindOfClass:[MessageView class]]){
|
|
messageView = (MessageView*)sender;
|
|
}
|
|
|
|
if (messageView && ![messageView isHit]){
|
|
messageView.hit = YES;
|
|
|
|
[UIView animateWithDuration:kMessageBarAnimationDuration animations:^{
|
|
[messageView setFrame:CGRectMake(messageView.frame.origin.x, messageView.frame.origin.y - [messageView height] - _messageBarOffset, [messageView width], [messageView height])]; // slide back up
|
|
} completion:^(BOOL finished) {
|
|
_messageVisible = NO;
|
|
[messageView removeFromSuperview];
|
|
|
|
if (itemHit){
|
|
if ([messageView.callbacks count] > 0){
|
|
id obj = [messageView.callbacks objectAtIndex:0];
|
|
if (![obj isEqual:[NSNull null]]) {
|
|
((void (^)())obj)();
|
|
}
|
|
}
|
|
}
|
|
|
|
if([_messageBarQueue count] > 0) {
|
|
[self showNextMessage];
|
|
}
|
|
}];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
static UIFont *titleFont = nil;
|
|
static UIColor *titleColor = nil;
|
|
|
|
static UIFont *descriptionFont = nil;
|
|
static UIColor *descriptionColor = nil;
|
|
|
|
@implementation MessageView
|
|
|
|
@synthesize titleString = _titleString;
|
|
@synthesize descriptionString = _descriptionString;
|
|
@synthesize messageType = _messageType;
|
|
@synthesize shadowView = _shadowView;
|
|
|
|
@synthesize hasCallback = _hasCallback;
|
|
@synthesize callbacks = _callbacks;
|
|
|
|
@synthesize hit = _hit;
|
|
|
|
@synthesize width = _width;
|
|
@synthesize height = _height;
|
|
|
|
@synthesize duration = _duration;
|
|
|
|
#pragma mark - Alloc/Init
|
|
|
|
- (id)initWithTitle:(NSString*)title description:(NSString*)description type:(MessageBarMessageType)type
|
|
{
|
|
self = [super initWithFrame:CGRectZero];
|
|
if (self){
|
|
|
|
self.backgroundColor = [UIColor clearColor];
|
|
self.clipsToBounds = NO;
|
|
self.userInteractionEnabled = YES;
|
|
|
|
_titleString = title;
|
|
_descriptionString = description;
|
|
_messageType = type;
|
|
|
|
titleFont = [UIFont boldSystemFontOfSize:16.0];
|
|
titleColor = [UIColor colorWithWhite:1.0 alpha:1.0];
|
|
|
|
descriptionFont = [UIFont systemFontOfSize:14.0];
|
|
descriptionColor = [UIColor colorWithWhite:1.0 alpha:1.0];
|
|
|
|
_height = 0.0;
|
|
_width = 0.0;
|
|
|
|
_hasCallback = NO;
|
|
_hit = NO;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - Drawing
|
|
|
|
- (void)drawRect:(CGRect)rect
|
|
{
|
|
[super drawRect:rect];
|
|
|
|
CGContextRef context = UIGraphicsGetCurrentContext();
|
|
|
|
// background fill
|
|
CGContextSaveGState(context);
|
|
{
|
|
[[MessageBarStyleSheet backgroundColorForMessageType:_messageType] set];
|
|
CGContextFillRect(context, rect);
|
|
}
|
|
CGContextRestoreGState(context);
|
|
|
|
// bottom stroke
|
|
CGContextSaveGState(context);
|
|
{
|
|
CGContextBeginPath(context);
|
|
CGContextMoveToPoint(context, 0, rect.size.height);
|
|
CGContextSetStrokeColorWithColor(context, [MessageBarStyleSheet strokeColorForMessageType:_messageType].CGColor);
|
|
CGContextSetLineWidth(context, 1.0);
|
|
CGContextAddLineToPoint(context, rect.size.width, rect.size.height);
|
|
CGContextStrokePath(context);
|
|
}
|
|
CGContextRestoreGState(context);
|
|
|
|
CGFloat xOffset = kMessageBarPadding;
|
|
CGFloat yOffset = kMessageBarPadding;
|
|
|
|
// icon
|
|
CGContextSaveGState(context);
|
|
{
|
|
[[MessageBarStyleSheet iconImageForMessageType:_messageType] drawInRect:CGRectMake(xOffset, yOffset, kMessageBarIconSize, kMessageBarIconSize)];
|
|
}
|
|
CGContextRestoreGState(context);
|
|
|
|
yOffset -= kMessageBarTextOffset;
|
|
xOffset += kMessageBarIconSize + kMessageBarPadding;
|
|
|
|
CGFloat maxWith = (rect.size.width - (kMessageBarPadding * 3) - kMessageBarIconSize);
|
|
|
|
CGSize titleLabelSize = [_titleString sizeWithFont:titleFont forWidth:maxWith lineBreakMode:NSLineBreakByTruncatingTail];
|
|
if (_titleString && !_descriptionString){
|
|
yOffset = ceil(rect.size.height * 0.5) - ceil(titleLabelSize.height * 0.5) - kMessageBarTextOffset;
|
|
}
|
|
[titleColor set];
|
|
[_titleString drawInRect:CGRectMake(xOffset, yOffset, titleLabelSize.width, titleLabelSize.height) withFont:titleFont lineBreakMode:NSLineBreakByTruncatingTail alignment:NSTextAlignmentLeft];
|
|
|
|
yOffset += titleLabelSize.height;
|
|
|
|
CGSize descriptionLabelSize = [_descriptionString sizeWithFont:descriptionFont constrainedToSize:CGSizeMake(maxWith, kMessageBarMaxDescriptionHeight) lineBreakMode:NSLineBreakByTruncatingTail];
|
|
[descriptionColor set];
|
|
[_descriptionString drawInRect:CGRectMake(xOffset, yOffset, descriptionLabelSize.width, descriptionLabelSize.height) withFont:descriptionFont lineBreakMode:NSLineBreakByTruncatingTail alignment:NSTextAlignmentLeft];
|
|
}
|
|
|
|
#pragma mark - Layout
|
|
|
|
- (void)layoutSubviews
|
|
{
|
|
[super layoutSubviews];
|
|
_shadowView.frame = CGRectMake(0, [self height], [self width], _shadowView.image.size.height);
|
|
}
|
|
|
|
#pragma mark - Getters
|
|
|
|
- (CGFloat)height
|
|
{
|
|
if (_height == 0){
|
|
CGFloat maxWith = ([self width] - (kMessageBarPadding * 3) - kMessageBarIconSize);
|
|
CGSize titleLabelSize = [_titleString sizeWithFont:titleFont forWidth:maxWith lineBreakMode:NSLineBreakByTruncatingTail];
|
|
CGSize descriptionLabelSize = [_descriptionString sizeWithFont:descriptionFont constrainedToSize:CGSizeMake(maxWith, 10000) lineBreakMode:NSLineBreakByTruncatingTail];
|
|
_height = MAX((kMessageBarPadding*2) + titleLabelSize.height + descriptionLabelSize.height, (kMessageBarPadding*2) + kMessageBarIconSize);
|
|
}
|
|
return _height;
|
|
}
|
|
|
|
- (CGFloat)width
|
|
{
|
|
if (_width == 0){
|
|
_width = [UIScreen mainScreen].bounds.size.width;
|
|
}
|
|
return _width;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation MessageBarStyleSheet
|
|
|
|
#pragma mark - Colors
|
|
|
|
+ (UIColor*)backgroundColorForMessageType:(MessageBarMessageType)type
|
|
{
|
|
UIColor *backgroundColor = nil;
|
|
switch (type) {
|
|
case MessageBarMessageTypeError:
|
|
backgroundColor = [UIColor colorWithRed:1.0 green:0.611 blue:0.0 alpha:kMessageBarAlpha]; // orange
|
|
break;
|
|
case MessageBarMessageTypeSuccess:
|
|
backgroundColor = [UIColor colorWithRed:0.0f green:0.831f blue:0.176f alpha:kMessageBarAlpha]; // green
|
|
break;
|
|
case MessageBarMessageTypeInfo:
|
|
backgroundColor = [UIColor colorWithRed:0.0 green:0.482 blue:1.0 alpha:kMessageBarAlpha]; // blue
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return backgroundColor;
|
|
}
|
|
|
|
+ (UIColor*)strokeColorForMessageType:(MessageBarMessageType)type
|
|
{
|
|
UIColor *strokeColor = nil;
|
|
switch (type) {
|
|
case MessageBarMessageTypeError:
|
|
strokeColor = [UIColor colorWithRed:0.949f green:0.580f blue:0.0f alpha:1.0f]; // orange
|
|
break;
|
|
case MessageBarMessageTypeSuccess:
|
|
strokeColor = [UIColor colorWithRed:0.0f green:0.772f blue:0.164f alpha:1.0f]; // orange
|
|
break;
|
|
case MessageBarMessageTypeInfo:
|
|
strokeColor = [UIColor colorWithRed:0.0f green:0.415f blue:0.803f alpha:1.0f]; // orange
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return strokeColor;
|
|
}
|
|
|
|
+ (UIImage*)iconImageForMessageType:(MessageBarMessageType)type
|
|
{
|
|
UIImage *iconImage = nil;
|
|
switch (type) {
|
|
case MessageBarMessageTypeError:
|
|
iconImage = [UIImage imageNamed:kImageIconError];
|
|
break;
|
|
case MessageBarMessageTypeSuccess:
|
|
iconImage = [UIImage imageNamed:kImageIconSucces];
|
|
break;
|
|
case MessageBarMessageTypeInfo:
|
|
iconImage = [UIImage imageNamed:kImageIconInfo];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return iconImage;
|
|
}
|
|
|
|
@end |