Skip to content

Instantly share code, notes, and snippets.

@westlinkin
Created January 18, 2016 07:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save westlinkin/0649a64d58147b58873d to your computer and use it in GitHub Desktop.
Save westlinkin/0649a64d58147b58873d to your computer and use it in GitHub Desktop.
A little weak of MTStatusBarOverlay to support iOS 9 and above
//
// MTStatusBarOverlay.m
//
// Created by Matthias Tretter on 27.09.10.
// Copyright (c) 2009-2011 Matthias Tretter, @myell0w. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Credits go to:
// -------------------------------
// http://stackoverflow.com/questions/2833724/adding-view-on-statusbar-in-iphone
// http://www.cocoabyss.com/uikit/custom-status-bar-ios/
// @reederapp for inspiration
// -------------------------------
#import "MTStatusBarOverlay.h"
#import <QuartzCore/QuartzCore.h>
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Functions
////////////////////////////////////////////////////////////////////////
NSData *MTStatusBarBackgroundImageData(BOOL shrinked);
unsigned char *MTStatusBarBackgroundImageArray(BOOL shrinked);
unsigned int MTStatusBarBackgroundImageLength(BOOL shrinked);
void mt_dispatch_sync_on_main_thread(dispatch_block_t block);
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Defines
////////////////////////////////////////////////////////////////////////
// macro to check if running on at least iOS 9
#define kMTIsOperatingSystemAtLeast9 [[NSProcessInfo processInfo] respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)] ? [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9.0, 0.0, 0.0}] : NO
#define kMTIsOperatingSystemAtLeast91 [[NSProcessInfo processInfo] respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)] ? [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9.0, 1.0, 0.0}] : NO
#define kMTIsOperatingSystemAtLeast92 [[NSProcessInfo processInfo] respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)] ? [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9.0, 2.0, 0.0}] : NO
// the height of the status bar
#define kStatusBarHeight 20.f
// width of the screen in present orientation
#define kScreenWidth [UIScreen mainScreen].nativeBounds.size.width/[UIScreen mainScreen].nativeScale
// height of the screen in present orientation
#define kScreenHeight [UIScreen mainScreen].nativeBounds.size.height/[UIScreen mainScreen].nativeScale
// macro for checking if we are on the iPad
#define IsIPad (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
// macro for checking if we are on the iPad in iPhone-Emulation mode
#define IsIPhoneEmulationMode (!IsIPad && \
MAX([UIApplication sharedApplication].statusBarFrame.size.width, [UIApplication sharedApplication].statusBarFrame.size.height) > 480.f)
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Customization
////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////
// Light Theme (for UIStatusBarStyleDefault)
///////////////////////////////////////////////////////
#define kLightThemeTextColor [UIColor blackColor]
#define kLightThemeErrorMessageTextColor [UIColor blackColor] // [UIColor colorWithRed:0.494898f green:0.330281f blue:0.314146f alpha:1.0f]
#define kLightThemeFinishedMessageTextColor [UIColor blackColor] // [UIColor colorWithRed:0.389487f green:0.484694f blue:0.38121f alpha:1.0f]
// iOS 7+ don't use shadow
//#define kLightThemeShadowColor [UIColor whiteColor]
//#define kLightThemeErrorMessageShadowColor [UIColor whiteColor]
//#define kLightThemeFinishedMessageShadowColor [UIColor whiteColor]
#define kLightThemeActivityIndicatorViewStyle UIActivityIndicatorViewStyleGray
#define kLightThemeDetailViewBackgroundColor [UIColor blackColor]
#define kLightThemeDetailViewBorderColor [UIColor darkGrayColor]
#define kLightThemeHistoryTextColor [UIColor colorWithRed:0.749f green:0.749f blue:0.749f alpha:1.0f]
///////////////////////////////////////////////////////
// Dark Theme (for UIStatusBarStyleBlackOpaque)
///////////////////////////////////////////////////////
#define kDarkThemeTextColor [UIColor colorWithRed:0.749f green:0.749f blue:0.749f alpha:1.0f]
#define kDarkThemeErrorMessageTextColor [UIColor colorWithRed:0.749f green:0.749f blue:0.749f alpha:1.0f] // [UIColor colorWithRed:0.918367f green:0.48385f blue:0.423895f alpha:1.0f]
#define kDarkThemeFinishedMessageTextColor [UIColor colorWithRed:0.749f green:0.749f blue:0.749f alpha:1.0f] // [UIColor colorWithRed:0.681767f green:0.918367f blue:0.726814f alpha:1.0f]
#define kDarkThemeActivityIndicatorViewStyle UIActivityIndicatorViewStyleWhite
#define kDarkThemeDetailViewBackgroundColor [UIColor colorWithRed:0.3f green:0.3f blue:0.3f alpha:1.0f]
#define kDarkThemeDetailViewBorderColor [UIColor whiteColor]
#define kDarkThemeHistoryTextColor [UIColor whiteColor]
///////////////////////////////////////////////////////
// Progress
///////////////////////////////////////////////////////
#define kProgressViewAlpha 0.4f
#define kProgressViewBackgroundColor [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:1.0f]
///////////////////////////////////////////////////////
// Animations
///////////////////////////////////////////////////////
// minimum time that a message is shown, when messages are queued
#define kMinimumMessageVisibleTime 0.4f
// duration of the animation to show next status message in seconds
#define kNextStatusAnimationDuration 0.6f
// duration the statusBarOverlay takes to appear when it was hidden
#define kAppearAnimationDuration 0.5f
// animation duration of animation mode shrink
#define kAnimationDurationShrink 0.3f
// animation duration of animation mode fallDown
#define kAnimationDurationFallDown 0.4f
// animation duration of change of progressView-size
#define kUpdateProgressViewDuration 0.2f
// delay after that the status bar gets visible again after rotation
#define kRotationAppearDelay [UIApplication sharedApplication].statusBarOrientationAnimationDuration
///////////////////////////////////////////////////////
// Text
///////////////////////////////////////////////////////
// Text that is displayed in the finished-Label when the finish was successful
#define kFinishedText @"✓"
#define kFinishedFontSize 22.f
// Text that is displayed when an error occured
#define kErrorText @"✗"
#define kErrorFontSize 19.f
///////////////////////////////////////////////////////
// Detail View
///////////////////////////////////////////////////////
#define kHistoryTableRowHeight 25.f
#define kMaxHistoryTableRowCount 5
#define kDetailViewAlpha 0.9f
#define kDetailViewWidth (IsIPad ? 400.f : 280.f)
// default frame of detail view when it is hidden
#define kDefaultDetailViewFrame CGRectMake((kScreenWidth - kDetailViewWidth)/2, -(kHistoryTableRowHeight*kMaxHistoryTableRowCount + kStatusBarHeight),\
kDetailViewWidth, kHistoryTableRowHeight*kMaxHistoryTableRowCount + kStatusBarHeight)
///////////////////////////////////////////////////////
// Size
///////////////////////////////////////////////////////
// Size of the text in the status labels
#define kStatusLabelSize 12.f
// default-width of the small-mode
#define kWidthSmall 26.f
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Class Extension
////////////////////////////////////////////////////////////////////////
@interface MTStatusBarOverlay ()
@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
@property (nonatomic, strong) UIImageView *statusBarBackgroundImageView;
@property (nonatomic, strong) UILabel *statusLabel1;
@property (nonatomic, strong) UILabel *statusLabel2;
@property (nonatomic, unsafe_unretained) UILabel *hiddenStatusLabel;
@property (unsafe_unretained, nonatomic, readonly) UILabel *visibleStatusLabel;
@property (nonatomic, strong) UIImageView *progressView;
@property (nonatomic, assign) CGRect oldBackgroundViewFrame;
// overwrite property for read-write-access
@property (assign, getter=isHideInProgress) BOOL hideInProgress;
@property (assign, getter=isActive) BOOL active;
// read out hidden-state using alpha-value and hidden-property
@property (nonatomic, readonly, getter=isReallyHidden) BOOL reallyHidden;
@property (nonatomic, strong) UITextView *detailTextView;
@property (nonatomic, strong) NSMutableArray *messageQueue;
// overwrite property for read-write-access
@property (nonatomic, strong) NSMutableArray *messageHistory;
@property (nonatomic, strong) UITableView *historyTableView;
@property (nonatomic, assign) BOOL forcedToHide;
// intern method that posts a new entry to the message-queue
- (void)postMessage:(NSString *)message type:(MTMessageType)messageType duration:(NSTimeInterval)duration animated:(BOOL)animated immediate:(BOOL)immediate;
// intern method that clears the messageQueue and then posts a new entry to it
- (void)postImmediateMessage:(NSString *)message type:(MTMessageType)messageType duration:(NSTimeInterval)duration animated:(BOOL)animated;
// intern method that does all the work of showing the next message in the queue
- (void)showNextMessage;
// is called when the user touches the statusbar
- (void)contentViewClicked:(UIGestureRecognizer *)gestureRecognizer;
// is called when the user swipes down the statusbar
- (void)contentViewSwipedUp:(UIGestureRecognizer *)gestureRecognizer;
- (void)contentViewSwipedDown:(UIGestureRecognizer *)gestureRecognizer;
// updates the current status bar background image for the given style and current size
- (void)setStatusBarBackgroundForStyle:(UIStatusBarStyle)style;
// updates the text-colors of the labels for the given style and message type
- (void)setColorSchemeForStatusBarStyle:(UIStatusBarStyle)style messageType:(MTMessageType)messageType;
// updates the visiblity of the activity indicator and finished-label depending on the type
- (void)updateUIForMessageType:(MTMessageType)messageType duration:(NSTimeInterval)duration;
// updates the size of the progressView to always cover only the displayed text-frame
- (void)updateProgressViewSizeForLabel:(UILabel *)label;
// calls the delegate when a switch from one message to another one occured
- (void)callDelegateWithNewMessage:(NSString *)newMessage;
// update the height of the detail text view according to new text
- (void)updateDetailTextViewHeight;
// shrink/expand the overlay
- (void)setShrinked:(BOOL)shrinked animated:(BOOL)animated;
// set hidden-state using alpha-value instead of hidden-property
- (void)setHidden:(BOOL)hidden useAlpha:(BOOL)useAlpha;
// used for performSelector:withObject:
- (void)setHiddenUsingAlpha:(BOOL)hidden;
// set hidden-state of detailView
- (void)setDetailViewHidden:(BOOL)hidden animated:(BOOL)animated;
// History-tracking
- (void)addMessageToHistory:(NSString *)message;
- (void)clearHistory;
// selectors
- (void)rotateToStatusBarFrame:(NSValue *)statusBarFrameValue;
- (void)didChangeStatusBarFrame:(NSNotification *)notification;
// Fix to not overlay Notification Center
- (void)applicationDidBecomeActive:(NSNotification *)notifaction;
- (void)applicationWillResignActive:(NSNotification *)notifaction;
// returns the current frame for the detail view depending on the interface orientation
- (CGRect)backgroundViewFrameForStatusBarInterfaceOrientation;
@end
@implementation MTStatusBarOverlay
@synthesize backgroundView = backgroundView_;
@synthesize detailView = detailView_;
@synthesize statusBarBackgroundImageView = statusBarBackgroundImageView_;
@synthesize statusLabel1 = statusLabel1_;
@synthesize statusLabel2 = statusLabel2_;
@synthesize hiddenStatusLabel = hiddenStatusLabel_;
@synthesize progress = progress_;
@synthesize progressView = progressView_;
@synthesize activityIndicator = activityIndicator_;
@synthesize finishedLabel = finishedLabel_;
@synthesize hidesActivity = hidesActivity_;
//@synthesize defaultStatusBarImage = defaultStatusBarImage_;
//@synthesize defaultStatusBarImageShrinked = defaultStatusBarImageShrinked_;
@synthesize smallFrame = smallFrame_;
@synthesize oldBackgroundViewFrame = oldBackgroundViewFrame_;
@synthesize animation = animation_;
@synthesize hideInProgress = hideInProgress_;
@synthesize active = active_;
@synthesize messageQueue = messageQueue_;
@synthesize canRemoveImmediateMessagesFromQueue = canRemoveImmediateMessagesFromQueue_;
@synthesize detailViewMode = detailViewMode_;
@synthesize detailText = detailText_;
@synthesize detailTextView = detailTextView_;
@synthesize messageHistory = messageHistory_;
@synthesize historyTableView = historyTableView_;
@synthesize delegate = delegate_;
@synthesize forcedToHide = forcedToHide_;
@synthesize lastPostedMessage = lastPostedMessage_;
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Lifecycle
////////////////////////////////////////////////////////////////////////
- (id)init {
if (self = [super init]) {
CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
// only use height of 20px even is status bar is doubled
statusBarFrame.size.height = statusBarFrame.size.height == 2 * kStatusBarHeight ? kStatusBarHeight : statusBarFrame.size.height;
// if we are on the iPad but in iPhone-Mode (non-universal-app) correct the width
if (IsIPhoneEmulationMode) {
statusBarFrame.size.width = 320.f;
}
// Place the window on the correct level and position
self.windowLevel = UIWindowLevelStatusBar + 1.f;
self.frame = statusBarFrame;
self.alpha = 0.f;
self.hidden = NO;
self.backgroundColor = [UIColor clearColor];
// Default Small size: just show Activity Indicator
smallFrame_ = CGRectMake(statusBarFrame.size.width - kWidthSmall, 0.f, kWidthSmall, statusBarFrame.size.height);
// Default-values
animation_ = MTStatusBarOverlayAnimationNone;
active_ = NO;
hidesActivity_ = NO;
forcedToHide_ = NO;
// the detail view that is shown when the user touches the status bar in animation mode "FallDown"
detailView_ = [[UIView alloc] initWithFrame:kDefaultDetailViewFrame];
detailView_.backgroundColor = [UIColor blackColor];
detailView_.alpha = kDetailViewAlpha;
detailView_.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
detailViewMode_ = MTDetailViewModeCustom;
// add rounded corners to detail-view
detailView_.layer.masksToBounds = YES;
detailView_.layer.cornerRadius = 10.f;
detailView_.layer.borderWidth = 2.5f;
// add shadow
/*detailView_.layer.shadowColor = [UIColor blackColor].CGColor;
detailView_.layer.shadowOpacity = 1.0f;
detailView_.layer.shadowRadius = 6.0f;
detailView_.layer.shadowOffset = CGSizeMake(0, 3);*/
// Detail Text label
detailTextView_ = [[UITextView alloc] initWithFrame:CGRectMake(0, kStatusBarHeight,
kDefaultDetailViewFrame.size.width, kDefaultDetailViewFrame.size.height - kStatusBarHeight)];
detailTextView_.backgroundColor = [UIColor clearColor];
detailTextView_.userInteractionEnabled = NO;
detailTextView_.hidden = detailViewMode_ != MTDetailViewModeDetailText;
[detailView_ addSubview:detailTextView_];
// Message History
messageHistory_ = [[NSMutableArray alloc] init];
historyTableView_ = [[UITableView alloc] initWithFrame:CGRectMake(0, kStatusBarHeight,
kDefaultDetailViewFrame.size.width, kDefaultDetailViewFrame.size.height - kStatusBarHeight)];
historyTableView_.dataSource = self;
historyTableView_.delegate = nil;
historyTableView_.rowHeight = kHistoryTableRowHeight;
historyTableView_.separatorStyle = UITableViewCellSeparatorStyleNone;
// make table view-background transparent
historyTableView_.backgroundColor = [UIColor clearColor];
historyTableView_.opaque = NO;
historyTableView_.hidden = detailViewMode_ != MTDetailViewModeHistory;
historyTableView_.backgroundView = nil;
[detailView_ addSubview:historyTableView_];
[self addSubview:detailView_];
// Create view that stores all the content
CGRect backgroundFrame = [self backgroundViewFrameForStatusBarInterfaceOrientation];
backgroundView_ = [[UIView alloc] initWithFrame:backgroundFrame];
backgroundView_.clipsToBounds = YES;
backgroundView_.autoresizingMask = UIViewAutoresizingFlexibleWidth;
oldBackgroundViewFrame_ = backgroundView_.frame;
// Add gesture recognizers
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(contentViewClicked:)];
//UISwipeGestureRecognizer *upGestureRecognizer = [[[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(contentViewSwipedUp:)] autorelease];
//UISwipeGestureRecognizer *downGestureRecognizer = [[[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(contentViewSwipedDown:)] autorelease];
//upGestureRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
//downGestureRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
[backgroundView_ addGestureRecognizer:tapGestureRecognizer];
//[detailView_ addGestureRecognizer:upGestureRecognizer];
//[self addGestureRecognizer:downGestureRecognizer];
// Images used as background when status bar style is Default
// defaultStatusBarImage_ = [UIImage imageWithData:MTStatusBarBackgroundImageData(NO)];
// defaultStatusBarImageShrinked_ = [UIImage imageWithData:MTStatusBarBackgroundImageData(YES)];
// Background-Image of the Content View
statusBarBackgroundImageView_ = [[UIImageView alloc] initWithFrame:backgroundView_.frame];
statusBarBackgroundImageView_.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
statusBarBackgroundImageView_.backgroundColor = [UIColor whiteColor];
[self addSubviewToBackgroundView:statusBarBackgroundImageView_];
// Activity Indicator
activityIndicator_ = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator_.frame = CGRectMake(6.f, 3.f, backgroundView_.frame.size.height - 6.f, backgroundView_.frame.size.height - 6.f);
activityIndicator_.hidesWhenStopped = YES;
// iOS 5 doesn't correctly resize the activityIndicator. Bug?
if ([activityIndicator_ respondsToSelector:@selector(setColor:)]) {
[activityIndicator_.layer setValue:[NSNumber numberWithFloat:0.75f] forKeyPath:@"transform.scale"];
}
[self addSubviewToBackgroundView:activityIndicator_];
// Finished-Label
finishedLabel_ = [[UILabel alloc] initWithFrame:CGRectMake(4.f, 1.f, backgroundView_.frame.size.height, backgroundView_.frame.size.height - 1.f)];
finishedLabel_.shadowOffset = CGSizeMake(0.f, 1.f);
finishedLabel_.backgroundColor = [UIColor clearColor];
finishedLabel_.hidden = YES;
finishedLabel_.text = kFinishedText;
#ifdef __IPHONE_6_0
finishedLabel_.textAlignment = NSTextAlignmentCenter;
#else
finishedLabel_.textAlignment = UITextAlignmentCenter;
#endif
finishedLabel_.font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:kFinishedFontSize];
finishedLabel_.adjustsFontSizeToFitWidth = YES;
[self addSubviewToBackgroundView:finishedLabel_];
// Status Label 1 is first visible
statusLabel1_ = [[UILabel alloc] initWithFrame:CGRectMake(30.f, 0.f, backgroundView_.frame.size.width - 60.f, backgroundView_.frame.size.height - 1.f)];
statusLabel1_.backgroundColor = [UIColor clearColor];
statusLabel1_.shadowOffset = CGSizeMake(0.f, 1.f);
statusLabel1_.font = [UIFont boldSystemFontOfSize:kStatusLabelSize];
statusLabel1_.numberOfLines = 1;
#ifdef __IPHONE_6_0
statusLabel1_.textAlignment = NSTextAlignmentCenter;
statusLabel1_.lineBreakMode = NSLineBreakByTruncatingTail;
#else
statusLabel1_.textAlignment = UITextAlignmentCenter;
statusLabel1_.lineBreakMode = UILineBreakModeTailTruncation;
#endif
statusLabel1_.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self addSubviewToBackgroundView:statusLabel1_];
// Status Label 2 is hidden
statusLabel2_ = [[UILabel alloc] initWithFrame:CGRectMake(30.f, backgroundView_.frame.size.height, backgroundView_.frame.size.width - 60.f, backgroundView_.frame.size.height - 1.f)];
statusLabel2_.shadowOffset = CGSizeMake(0.f, 1.f);
statusLabel2_.backgroundColor = [UIColor clearColor];
statusLabel2_.font = [UIFont boldSystemFontOfSize:kStatusLabelSize];
statusLabel2_.numberOfLines = 1;
#ifdef __IPHONE_6_0
statusLabel2_.textAlignment = NSTextAlignmentCenter;
statusLabel2_.lineBreakMode = NSLineBreakByTruncatingTail;
#else
statusLabel2_.textAlignment = UITextAlignmentCenter;
statusLabel2_.lineBreakMode = UILineBreakModeTailTruncation;
#endif
statusLabel2_.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self addSubviewToBackgroundView:statusLabel2_];
// the hidden status label at the beginning
hiddenStatusLabel_ = statusLabel2_;
progress_ = 1.0;
progressView_ = [[UIImageView alloc] initWithFrame:statusBarBackgroundImageView_.frame];
progressView_.opaque = NO;
progressView_.hidden = YES;
progressView_.alpha = kProgressViewAlpha;
[self addSubviewToBackgroundView:progressView_];
messageQueue_ = [[NSMutableArray alloc] init];
canRemoveImmediateMessagesFromQueue_ = YES;
[self addSubview:backgroundView_];
// listen for changes of status bar frame
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didChangeStatusBarFrame:)
name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification object:nil];
// initial rotation, fixes the issue with a wrong bar appearance in landscape only mode
[self rotateToStatusBarFrame:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
delegate_ = nil;
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UIWindow
////////////////////////////////////////////////////////////////////////
- (UIViewController *)rootViewController {
return [UIApplication sharedApplication].delegate.window.rootViewController;
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Status Bar Appearance
////////////////////////////////////////////////////////////////////////
- (void)addSubviewToBackgroundView:(UIView *)view {
view.userInteractionEnabled = NO;
[self.backgroundView addSubview:view];
}
- (void)addSubviewToBackgroundView:(UIView *)view atIndex:(NSInteger)index {
view.userInteractionEnabled = NO;
[self.backgroundView insertSubview:view atIndex:index];
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Save/Restore current State
////////////////////////////////////////////////////////////////////////
- (void)saveState {
[self saveStateSynchronized:YES];
}
- (void)saveStateSynchronized:(BOOL)synchronizeAtEnd {
// TODO: save more state
[[NSUserDefaults standardUserDefaults] setBool:self.shrinked forKey:kMTStatusBarOverlayStateShrinked];
if (synchronizeAtEnd) {
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
- (void)restoreState {
// restore shrinked-state
[self setShrinked:[[NSUserDefaults standardUserDefaults] boolForKey:kMTStatusBarOverlayStateShrinked] animated:NO];
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Message Posting
////////////////////////////////////////////////////////////////////////
- (void)postMessage:(NSString *)message {
[self postMessage:message animated:YES];
}
- (void)postMessage:(NSString *)message animated:(BOOL)animated {
[self postMessage:message type:MTMessageTypeActivity duration:0 animated:animated immediate:NO];
}
- (void)postMessage:(NSString *)message duration:(NSTimeInterval)duration {
[self postMessage:message type:MTMessageTypeActivity duration:duration animated:YES immediate:NO];
}
- (void)postMessage:(NSString *)message duration:(NSTimeInterval)duration animated:(BOOL)animated {
[self postMessage:message type:MTMessageTypeActivity duration:duration animated:animated immediate:NO];
}
- (void)postImmediateMessage:(NSString *)message animated:(BOOL)animated {
[self postImmediateMessage:message type:MTMessageTypeActivity duration:0 animated:animated];
}
- (void)postImmediateMessage:(NSString *)message duration:(NSTimeInterval)duration {
[self postImmediateMessage:message type:MTMessageTypeActivity duration:duration animated:YES];
}
- (void)postImmediateMessage:(NSString *)message duration:(NSTimeInterval)duration animated:(BOOL)animated {
[self postImmediateMessage:message type:MTMessageTypeActivity duration:duration animated:animated];
}
- (void)postFinishMessage:(NSString *)message duration:(NSTimeInterval)duration {
[self postFinishMessage:message duration:duration animated:YES];
}
- (void)postFinishMessage:(NSString *)message duration:(NSTimeInterval)duration animated:(BOOL)animated {
[self postMessage:message type:MTMessageTypeFinish duration:duration animated:animated immediate:NO];
}
- (void)postImmediateFinishMessage:(NSString *)message duration:(NSTimeInterval)duration animated:(BOOL)animated {
[self postImmediateMessage:message type:MTMessageTypeFinish duration:duration animated:animated];
}
- (void)postErrorMessage:(NSString *)message duration:(NSTimeInterval)duration {
[self postErrorMessage:message duration:duration animated:YES];
}
- (void)postErrorMessage:(NSString *)message duration:(NSTimeInterval)duration animated:(BOOL)animated {
[self postMessage:message type:MTMessageTypeError duration:duration animated:animated immediate:NO];
}
- (void)postImmediateErrorMessage:(NSString *)message duration:(NSTimeInterval)duration animated:(BOOL)animated {
[self postImmediateMessage:message type:MTMessageTypeError duration:duration animated:animated];
}
- (void)postMessageDictionary:(NSDictionary *)messageDictionary {
[self postMessage:[messageDictionary valueForKey:kMTStatusBarOverlayMessageKey]
type:[[messageDictionary valueForKey:kMTStatusBarOverlayMessageTypeKey] intValue]
duration:[[messageDictionary valueForKey:kMTStatusBarOverlayDurationKey] doubleValue]
animated:[[messageDictionary valueForKey:kMTStatusBarOverlayAnimationKey] boolValue]
immediate:[[messageDictionary valueForKey:kMTStatusBarOverlayImmediateKey] boolValue]];
}
- (void)postMessage:(NSString *)message type:(MTMessageType)messageType duration:(NSTimeInterval)duration animated:(BOOL)animated immediate:(BOOL)immediate {
mt_dispatch_sync_on_main_thread(^{
// don't add to queue when message is empty
if (message.length == 0) {
return;
}
NSDictionary *messageDictionaryRepresentation = [NSDictionary dictionaryWithObjectsAndKeys:message, kMTStatusBarOverlayMessageKey,
[NSNumber numberWithInt:messageType], kMTStatusBarOverlayMessageTypeKey,
[NSNumber numberWithDouble:duration], kMTStatusBarOverlayDurationKey,
[NSNumber numberWithBool:animated], kMTStatusBarOverlayAnimationKey,
[NSNumber numberWithBool:immediate], kMTStatusBarOverlayImmediateKey, nil];
@synchronized (self.messageQueue) {
[self.messageQueue insertObject:messageDictionaryRepresentation atIndex:0];
}
// if the overlay is currently not active, begin with showing of messages
if (!self.active) {
[self showNextMessage];
}
});
}
- (void)postImmediateMessage:(NSString *)message type:(MTMessageType)messageType duration:(NSTimeInterval)duration animated:(BOOL)animated {
@synchronized (self.messageQueue) {
NSMutableArray *clearedMessages = [NSMutableArray array];
for (id messageDictionary in self.messageQueue) {
if (messageDictionary != [self.messageQueue lastObject] &&
(self.canRemoveImmediateMessagesFromQueue || [[messageDictionary valueForKey:kMTStatusBarOverlayImmediateKey] boolValue] == NO)) {
[clearedMessages addObject:messageDictionary];
}
}
[self.messageQueue removeObjectsInArray:clearedMessages];
// call delegate
if ([self.delegate respondsToSelector:@selector(statusBarOverlayDidClearMessageQueue:)] && clearedMessages.count > 0) {
[self.delegate statusBarOverlayDidClearMessageQueue:clearedMessages];
}
}
[self postMessage:message type:messageType duration:duration animated:animated immediate:YES];
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Showing Next Message
////////////////////////////////////////////////////////////////////////
- (void)showNextMessage {
if (self.forcedToHide) {
return;
}
// if there is no next message to show overlay is not active anymore
@synchronized (self.messageQueue) {
if ([self.messageQueue count] < 1) {
self.active = NO;
return;
}
}
// there is a next message, overlay is active
self.active = YES;
NSDictionary *nextMessageDictionary = nil;
// read out next message
@synchronized (self.messageQueue) {
nextMessageDictionary = [self.messageQueue lastObject];
}
NSString *message = [nextMessageDictionary valueForKey:kMTStatusBarOverlayMessageKey];
MTMessageType messageType = (MTMessageType) [[nextMessageDictionary valueForKey:kMTStatusBarOverlayMessageTypeKey] intValue];
NSTimeInterval duration = (NSTimeInterval) [[nextMessageDictionary valueForKey:kMTStatusBarOverlayDurationKey] doubleValue];
BOOL animated = [[nextMessageDictionary valueForKey:kMTStatusBarOverlayAnimationKey] boolValue];
// don't show anything if status bar is hidden (queue gets cleared)
if ([UIApplication sharedApplication].statusBarHidden) {
@synchronized (self.messageQueue) {
[self.messageQueue removeAllObjects];
}
self.active = NO;
return;
}
// don't duplicate animation if already displaying with text
if (!self.reallyHidden && [self.visibleStatusLabel.text isEqualToString:message]) {
// remove unneccesary message
@synchronized (self.messageQueue) {
if (self.messageQueue.count > 0)
[self.messageQueue removeLastObject];
}
// show the next message w/o delay
[self showNextMessage];
return;
}
// cancel previous hide- and clear requests
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hide) object:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(clearHistory) object:nil];
// update UI depending on current status bar style
UIStatusBarStyle statusBarStyle = [UIApplication sharedApplication].statusBarStyle;
[self setStatusBarBackgroundForStyle:statusBarStyle];
[self setColorSchemeForStatusBarStyle:statusBarStyle messageType:messageType];
[self updateUIForMessageType:messageType duration:duration];
// if status bar is currently hidden, show it unless it is forced to hide
if (self.reallyHidden) {
// clear currently visible status label
self.visibleStatusLabel.text = @"";
// show status bar overlay with animation
[UIView animateWithDuration:self.shrinked ? 0 : kAppearAnimationDuration
animations:^{
[self setHidden:NO useAlpha:YES];
}];
}
if (animated) {
// set text of currently not visible label to new text
self.hiddenStatusLabel.text = message;
// update progressView to only cover displayed text
[self updateProgressViewSizeForLabel:self.hiddenStatusLabel];
// position hidden status label under visible status label
self.hiddenStatusLabel.frame = CGRectMake(self.hiddenStatusLabel.frame.origin.x,
kStatusBarHeight,
self.hiddenStatusLabel.frame.size.width,
self.hiddenStatusLabel.frame.size.height);
// animate hidden label into user view and visible status label out of view
[UIView animateWithDuration:kNextStatusAnimationDuration
delay:0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
// move both status labels up
self.statusLabel1.frame = CGRectMake(self.statusLabel1.frame.origin.x,
self.statusLabel1.frame.origin.y - kStatusBarHeight,
self.statusLabel1.frame.size.width,
self.statusLabel1.frame.size.height);
self.statusLabel2.frame = CGRectMake(self.statusLabel2.frame.origin.x,
self.statusLabel2.frame.origin.y - kStatusBarHeight,
self.statusLabel2.frame.size.width,
self.statusLabel2.frame.size.height);
}
completion:^(BOOL finished) {
// add old message to history
[self addMessageToHistory:self.visibleStatusLabel.text];
// after animation, set new hidden status label indicator
if (self.hiddenStatusLabel == self.statusLabel1) {
self.hiddenStatusLabel = self.statusLabel2;
} else {
self.hiddenStatusLabel = self.statusLabel1;
}
// remove the message from the queue
@synchronized (self.messageQueue) {
if (self.messageQueue.count > 0)
[self.messageQueue removeLastObject];
}
// inform delegate about message-switch
[self callDelegateWithNewMessage:message];
// show the next message
[self performSelector:@selector(showNextMessage) withObject:nil afterDelay:kMinimumMessageVisibleTime];
}];
}
// w/o animation just save old text and set new one
else {
// add old message to history
[self addMessageToHistory:self.visibleStatusLabel.text];
// set new text
self.visibleStatusLabel.text = message;
// update progressView to only cover displayed text
[self updateProgressViewSizeForLabel:self.visibleStatusLabel];
// remove the message from the queue
@synchronized (self.messageQueue) {
if (self.messageQueue.count > 0)
[self.messageQueue removeLastObject];
}
// inform delegate about message-switch
[self callDelegateWithNewMessage:message];
// show next message
[self performSelector:@selector(showNextMessage) withObject:nil afterDelay:kMinimumMessageVisibleTime];
}
self.lastPostedMessage = message;
}
- (void)hide {
[self.activityIndicator stopAnimating];
self.statusLabel1.text = @"";
self.statusLabel2.text = @"";
self.hideInProgress = NO;
// cancel previous hide- and clear requests
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hide) object:nil];
// hide detailView
[self setDetailViewHidden:YES animated:YES];
// hide status bar overlay with animation
[UIView animateWithDuration:self.shrinked ? 0. : kAppearAnimationDuration
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
[self setHidden:YES useAlpha:YES];
} completion:^(BOOL finished) {
// call delegate
if ([self.delegate respondsToSelector:@selector(statusBarOverlayDidHide)]) {
[self.delegate statusBarOverlayDidHide];
}
}];
}
- (void)hideTemporary {
self.forcedToHide = YES;
// hide status bar overlay with animation
[UIView animateWithDuration:self.shrinked ? 0. : kAppearAnimationDuration animations:^{
[self setHidden:YES useAlpha:YES];
}];
}
// this shows the status bar overlay, if there is text to show
- (void)show {
self.forcedToHide = NO;
if (self.reallyHidden) {
if (self.visibleStatusLabel.text.length > 0) {
// show status bar overlay with animation
[UIView animateWithDuration:self.shrinked ? 0. : kAppearAnimationDuration animations:^{
[self setHidden:NO useAlpha:YES];
}];
}
[self showNextMessage];
}
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Rotation
////////////////////////////////////////////////////////////////////////
- (void)didChangeStatusBarFrame:(NSNotification *)notification {
NSValue *statusBarFrameValue = [notification.userInfo valueForKey:UIApplicationStatusBarFrameUserInfoKey];
// TODO: react on changes of status bar height (e.g. incoming call, tethering, ...)
// NSLog(@"Status bar frame changed: %@", NSStringFromCGRect([statusBarFrameValue CGRectValue]));
// have to use performSelector to prohibit animation of rotation
[self performSelector:@selector(rotateToStatusBarFrame:) withObject:statusBarFrameValue afterDelay:0];
}
// works only for iPad
- (void)rotateToStatusBarFrame:(NSValue *)statusBarFrameValue {
// current interface orientation
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
// is the statusBar visible before rotation?
BOOL visibleBeforeTransformation = !self.reallyHidden;
// store a flag, if the StatusBar is currently shrinked
BOOL shrinkedBeforeTransformation = self.shrinked;
// hide and then unhide after rotation
if (visibleBeforeTransformation) {
[self setHidden:YES useAlpha:YES];
[self setDetailViewHidden:YES animated:NO];
}
CGFloat pi = (CGFloat) M_PI;
if (orientation == UIDeviceOrientationPortrait) {
self.transform = CGAffineTransformIdentity;
if ((BOOL) (kMTIsOperatingSystemAtLeast9)) {
self.frame = CGRectMake(0.f, 0.f, MIN(kScreenWidth, kScreenHeight), kStatusBarHeight);
self.smallFrame = CGRectMake(self.frame.size.width - kWidthSmall, 0.0f, kWidthSmall, self.frame.size.height);
} else {
self.frame = CGRectMake(0.f, 0.f, kScreenWidth, kStatusBarHeight);
self.smallFrame = CGRectMake(self.frame.size.width - kWidthSmall, 0.0f, kWidthSmall, self.frame.size.height);
}
} else if (orientation == UIDeviceOrientationLandscapeLeft) {
if ((BOOL) (kMTIsOperatingSystemAtLeast9)) {
if ((BOOL) (kMTIsOperatingSystemAtLeast91) && !(BOOL)(kMTIsOperatingSystemAtLeast92)) {
self.transform = CGAffineTransformMakeRotation(pi * (90.f) / 180.0f);
self.frame = CGRectMake(kScreenWidth - kStatusBarHeight, 0, kStatusBarHeight, kScreenHeight);
self.smallFrame = CGRectMake(MAX(kScreenHeight, kScreenWidth) - kWidthSmall, 0, kWidthSmall, kStatusBarHeight);
} else {
self.transform = CGAffineTransformIdentity;
self.frame = CGRectMake(0, 0, MAX(kScreenHeight, kScreenWidth), kScreenHeight);
self.smallFrame = CGRectMake(MAX(kScreenHeight, kScreenWidth) - kWidthSmall, 0, kWidthSmall, kStatusBarHeight);
}
} else {
self.transform = CGAffineTransformMakeRotation(pi * (90.f) / 180.0f);
self.frame = CGRectMake(kScreenWidth - kStatusBarHeight, 0, kStatusBarHeight, kScreenHeight);
self.smallFrame = CGRectMake(kScreenHeight - kWidthSmall, 0, kWidthSmall, kStatusBarHeight);
}
} else if (orientation == UIDeviceOrientationLandscapeRight) {
if ((BOOL) (kMTIsOperatingSystemAtLeast9)) {
if ((BOOL) (kMTIsOperatingSystemAtLeast91) && !(BOOL)(kMTIsOperatingSystemAtLeast92)) {
self.transform = CGAffineTransformMakeRotation(pi * (-90.f) / 180.0f);
self.frame = CGRectMake(0, 0, MAX(kScreenHeight, kScreenWidth), kScreenHeight);
self.smallFrame = CGRectMake(MAX(kScreenHeight, kScreenWidth) - kWidthSmall, 0.f, kWidthSmall, kStatusBarHeight);
} else {
self.transform = CGAffineTransformIdentity;
self.frame = CGRectMake(0, 0, MAX(kScreenHeight, kScreenWidth), kScreenHeight);
self.smallFrame = CGRectMake(MAX(kScreenHeight, kScreenWidth) - kWidthSmall, 0.f, kWidthSmall, kStatusBarHeight);
}
} else {
self.transform = CGAffineTransformMakeRotation(pi * (-90.f) / 180.0f);
self.frame = CGRectMake(0.f, 0.f, kStatusBarHeight, kScreenHeight);
self.smallFrame = CGRectMake(kScreenHeight - kWidthSmall, 0.f, kWidthSmall, kStatusBarHeight);
}
} else if (orientation == UIDeviceOrientationPortraitUpsideDown) {
if ((BOOL) (kMTIsOperatingSystemAtLeast9)) {
if ((BOOL) (kMTIsOperatingSystemAtLeast91) && !(BOOL)(kMTIsOperatingSystemAtLeast92)) {
self.transform = CGAffineTransformMakeRotation(pi);
self.frame = CGRectMake(0.f, kScreenHeight - kStatusBarHeight, MIN(kScreenWidth, kScreenHeight), kStatusBarHeight);
self.smallFrame = CGRectMake(self.frame.size.width - kWidthSmall, 0.f, kWidthSmall, self.frame.size.height);
} else {
self.transform = CGAffineTransformIdentity;
self.frame = CGRectMake(0.f, 0.f, MIN(kScreenWidth, kScreenHeight), kStatusBarHeight);
self.smallFrame = CGRectMake(self.frame.size.width - kWidthSmall, 0.f, kWidthSmall, self.frame.size.height);
}
} else {
self.transform = CGAffineTransformMakeRotation(pi);
self.frame = CGRectMake(0.f, kScreenHeight - kStatusBarHeight, kScreenWidth, kStatusBarHeight);
self.smallFrame = CGRectMake(self.frame.size.width - kWidthSmall, 0.f, kWidthSmall, self.frame.size.height);
}
}
self.backgroundView.frame = [self backgroundViewFrameForStatusBarInterfaceOrientation];
// if the statusBar is currently shrinked, update the frames for the new rotation state
if (shrinkedBeforeTransformation) {
// the oldBackgroundViewFrame is the frame of the whole StatusBar
self.oldBackgroundViewFrame = CGRectMake(0.f, 0.f, UIInterfaceOrientationIsPortrait(orientation) ? kScreenWidth : kScreenHeight, kStatusBarHeight);
// the backgroundView gets the newly computed smallFrame
self.backgroundView.frame = self.smallFrame;
}
// make visible after given time
if (visibleBeforeTransformation) {
// TODO:
// somehow this doesn't work anymore since rotation-method was changed from
// DeviceDidRotate-Notification to StatusBarFrameChanged-Notification
// therefore iplemented it with a UIView-Animation instead
//[self performSelector:@selector(setHiddenUsingAlpha:) withObject:[NSNumber numberWithBool:NO] afterDelay:kRotationAppearDelay];
[UIView animateWithDuration:kAppearAnimationDuration
delay:kRotationAppearDelay
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[self setHiddenUsingAlpha:NO];
}
completion:NULL];
}
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Setter/Getter
////////////////////////////////////////////////////////////////////////
- (void)setProgress:(double)progress {
// bound progress to 0.0 - 1.0
progress = MAX(0.0, MIN(progress, 1.0));
// do not decrease progress if it is no reset
if (progress == 0.0 || progress > progress_) {
progress_ = progress;
}
// update UI on main thread
[self performSelectorOnMainThread:@selector(updateProgressViewSizeForLabel:) withObject:self.visibleStatusLabel waitUntilDone:NO];
}
- (void)setDetailText:(NSString *)detailText {
if (detailText_ != detailText) {
detailText_ = [detailText copy];
// update text in label
self.detailTextView.text = detailText;
// update height of detailText-View
[self updateDetailTextViewHeight];
// update height of detailView
[self setDetailViewHidden:self.detailViewHidden animated:YES];
}
}
- (void)setDetailViewMode:(MTDetailViewMode)detailViewMode {
detailViewMode_ = detailViewMode;
// update UI
self.historyTableView.hidden = detailViewMode != MTDetailViewModeHistory;
self.detailTextView.hidden = detailViewMode != MTDetailViewModeDetailText;
}
- (void)setAnimation:(MTStatusBarOverlayAnimation)animation {
animation_ = animation;
// update appearance according to new animation-mode
// if new animation mode is shrink or none, the detailView mustn't be visible
if (animation == MTStatusBarOverlayAnimationShrink || animation == MTStatusBarOverlayAnimationNone) {
// detailView currently visible -> hide it
if (!self.detailViewHidden) {
[self setDetailViewHidden:YES animated:YES];
}
}
// if new animation mode is fallDown, the overlay must be extended
if (animation == MTStatusBarOverlayAnimationFallDown) {
if (self.shrinked) {
[self setShrinked:NO animated:YES];
}
}
}
- (BOOL)isShrinked {
return self.backgroundView.frame.size.width == self.smallFrame.size.width;
}
- (void)setShrinked:(BOOL)shrinked animated:(BOOL)animated {
[UIView animateWithDuration:animated ? kAnimationDurationShrink : 0.
animations:^{
// shrink the overlay
if (shrinked) {
self.oldBackgroundViewFrame = self.backgroundView.frame;
self.backgroundView.frame = self.smallFrame;
self.statusLabel1.hidden = YES;
self.statusLabel2.hidden = YES;
}
// expand the overlay
else {
self.backgroundView.frame = self.oldBackgroundViewFrame;
self.statusLabel1.hidden = NO;
self.statusLabel2.hidden = NO;
if ([activityIndicator_ respondsToSelector:@selector(setColor:)]) {
CGRect frame = self.statusLabel1.frame;
frame.size.width = self.backgroundView.frame.size.width - 60.f;
self.statusLabel1.frame = frame;
frame = self.statusLabel2.frame;
frame.size.width = self.backgroundView.frame.size.width - 60.f;
self.statusLabel2.frame = frame;
}
}
// update status bar background
[self setStatusBarBackgroundForStyle:[UIApplication sharedApplication].statusBarStyle];
}];
}
- (BOOL)isDetailViewHidden {
return self.detailView.hidden == YES || self.detailView.alpha == 0.f ||
self.detailView.frame.origin.y + self.detailView.frame.size.height < kStatusBarHeight;
}
- (void)setDetailViewHidden:(BOOL)hidden animated:(BOOL)animated {
// hide detail view
if (hidden) {
[UIView animateWithDuration:animated ? kAnimationDurationFallDown : 0.
delay:0.
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.detailView.frame = CGRectMake(self.detailView.frame.origin.x, -self.detailView.frame.size.height,
self.detailView.frame.size.width, self.detailView.frame.size.height);
}
completion:NULL];
}
// show detail view
else {
[UIView animateWithDuration:animated ? kAnimationDurationFallDown : 0.
delay:0.
options:UIViewAnimationOptionCurveEaseIn
animations:^{
int y = 0;
// if history is enabled let the detailView "grow" with
// the number of messages in the history up until the set maximum
if (self.detailViewMode == MTDetailViewModeHistory) {
y = -(kMaxHistoryTableRowCount - MIN(self.messageHistory.count, kMaxHistoryTableRowCount)) * kHistoryTableRowHeight;
self.historyTableView.frame = CGRectMake(self.historyTableView.frame.origin.x, kStatusBarHeight - y,
self.historyTableView.frame.size.width, self.historyTableView.frame.size.height);
}
if (self.detailViewMode == MTDetailViewModeDetailText) {
self.detailView.frame = CGRectMake(self.detailView.frame.origin.x, y,
self.detailView.frame.size.width, self.detailTextView.frame.size.height + kStatusBarHeight);
} else {
self.detailView.frame = CGRectMake(self.detailView.frame.origin.x, y,
self.detailView.frame.size.width, self.detailView.frame.size.height);
}
}
completion:NULL];
}
}
- (UILabel *)visibleStatusLabel {
if (self.hiddenStatusLabel == self.statusLabel1) {
return self.statusLabel2;
}
return self.statusLabel1;
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UITableViewDataSource
////////////////////////////////////////////////////////////////////////
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.messageHistory.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID = @"MTStatusBarOverlayHistoryCellID";
UITableViewCell *cell = nil;
// step 1: is there a reusable cell?
cell = [tableView dequeueReusableCellWithIdentifier:cellID];
// step 2: no? -> create new cell
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellID];
cell.textLabel.font = [UIFont boldSystemFontOfSize:10];
cell.textLabel.textColor = [UIApplication sharedApplication].statusBarStyle == UIStatusBarStyleDefault ? kLightThemeHistoryTextColor : kDarkThemeHistoryTextColor;
cell.detailTextLabel.font = [UIFont boldSystemFontOfSize:12];
cell.detailTextLabel.textColor = [UIApplication sharedApplication].statusBarStyle == UIStatusBarStyleDefault ? kLightThemeHistoryTextColor : kDarkThemeHistoryTextColor;
}
// step 3: set up cell value
cell.textLabel.text = [self.messageHistory objectAtIndex:indexPath.row];
cell.detailTextLabel.text = kFinishedText;
return cell;
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Gesture Recognizer
////////////////////////////////////////////////////////////////////////
- (void)contentViewClicked:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
// if we are currently in a special state, restore to normal
// and ignore current set animation in that case
if (self.shrinked) {
[self setShrinked:NO animated:YES];
} else if (!self.detailViewHidden) {
[self setDetailViewHidden:YES animated:YES];
} else {
// normal case/status, do what's specified in animation-state
switch (self.animation) {
case MTStatusBarOverlayAnimationShrink:
[self setShrinked:!self.shrinked animated:YES];
break;
case MTStatusBarOverlayAnimationFallDown:
// detailView currently visible -> hide it
[self setDetailViewHidden:!self.detailViewHidden animated:YES];
break;
case MTStatusBarOverlayAnimationNone:
// ignore
break;
}
}
if ([self.delegate respondsToSelector:@selector(statusBarOverlayDidRecognizeGesture:)]) {
[self.delegate statusBarOverlayDidRecognizeGesture:gestureRecognizer];
}
}
}
- (void)contentViewSwipedUp:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
[self setDetailViewHidden:YES animated:YES];
if ([self.delegate respondsToSelector:@selector(statusBarOverlayDidRecognizeGesture:)]) {
[self.delegate statusBarOverlayDidRecognizeGesture:gestureRecognizer];
}
}
}
- (void)contentViewSwipedDown:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
[self setDetailViewHidden:NO animated:YES];
if ([self.delegate respondsToSelector:@selector(statusBarOverlayDidRecognizeGesture:)]) {
[self.delegate statusBarOverlayDidRecognizeGesture:gestureRecognizer];
}
}
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UIApplication Notifications
////////////////////////////////////////////////////////////////////////
- (void)applicationWillResignActive:(NSNotification *)notifaction {
// We hide temporary when the application resigns active s.t the overlay
// doesn't overlay the Notification Center. Let's hope this helps AppStore
// Approval ...
[self hideTemporary];
}
- (void)applicationDidBecomeActive:(NSNotification *)notifaction {
[self show];
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Private Methods
////////////////////////////////////////////////////////////////////////
- (void)setStatusBarBackgroundForStyle:(UIStatusBarStyle)style {
// gray status bar?
// on iPad the Default Status Bar Style is black too
if (style == UIStatusBarStyleDefault && !IsIPad && !IsIPhoneEmulationMode) {
// choose image depending on size
// if (self.shrinked) {
// self.statusBarBackgroundImageView.image = [self.defaultStatusBarImageShrinked stretchableImageWithLeftCapWidth:2.0f topCapHeight:0.0f];
// } else {
// self.statusBarBackgroundImageView.image = [self.defaultStatusBarImage stretchableImageWithLeftCapWidth:2.0f topCapHeight:0.0f];
// }
// statusBarBackgroundImageView_.backgroundColor = [UIColor clearColor];
self.statusBarBackgroundImageView.image = nil;
statusBarBackgroundImageView_.backgroundColor = _customBackgroundColor ? _customBackgroundColor : [UIColor whiteColor];
}
// black status bar? -> no image
else {
self.statusBarBackgroundImageView.image = nil;
statusBarBackgroundImageView_.backgroundColor = _customBackgroundColor ? _customBackgroundColor : [UIColor blackColor];
}
}
// for labels
- (void)setColorSchemeForStatusBarStyle:(UIStatusBarStyle)style messageType:(MTMessageType)messageType {
// UIStatusBarStyleDefault = 0, // Dark content, for use on light backgrounds
// UIStatusBarStyleLightContent NS_ENUM_AVAILABLE_IOS(7_0) = 1, // Light content, for use on dark backgrounds
// gray status bar?
// on iPad the Default Status Bar Style is black too
if (style == UIStatusBarStyleDefault && !IsIPad && !IsIPhoneEmulationMode) {
switch (messageType) {
case MTMessageTypeFinish:
self.statusLabel1.textColor = kLightThemeFinishedMessageTextColor;
self.statusLabel2.textColor = kLightThemeFinishedMessageTextColor;
self.finishedLabel.textColor = kLightThemeFinishedMessageTextColor;
break;
case MTMessageTypeError:
self.statusLabel1.textColor = kLightThemeErrorMessageTextColor;
self.statusLabel2.textColor = kLightThemeErrorMessageTextColor;
self.finishedLabel.textColor = kLightThemeErrorMessageTextColor;
break;
default:
self.statusLabel1.textColor = kLightThemeTextColor;
self.statusLabel2.textColor = kLightThemeTextColor;
self.finishedLabel.textColor = kLightThemeTextColor;
break;
}
self.activityIndicator.activityIndicatorViewStyle = kLightThemeActivityIndicatorViewStyle;
if ([self.activityIndicator respondsToSelector:@selector(setColor:)]) {
[self.activityIndicator setColor:kLightThemeTextColor];
}
self.detailView.backgroundColor = kLightThemeDetailViewBackgroundColor;
self.detailView.layer.borderColor = [kLightThemeDetailViewBorderColor CGColor];
self.historyTableView.separatorColor = kLightThemeDetailViewBorderColor;
self.detailTextView.textColor = kLightThemeHistoryTextColor;
self.progressView.backgroundColor = [UIColor clearColor];
} else {
// Light content, for use on dark backgrounds
switch (messageType) {
case MTMessageTypeFinish:
self.statusLabel1.textColor = kDarkThemeFinishedMessageTextColor;
self.statusLabel2.textColor = kDarkThemeFinishedMessageTextColor;
self.finishedLabel.textColor = kDarkThemeFinishedMessageTextColor;
break;
case MTMessageTypeError:
self.statusLabel1.textColor = kDarkThemeErrorMessageTextColor;
self.statusLabel2.textColor = kDarkThemeErrorMessageTextColor;
self.finishedLabel.textColor = kDarkThemeErrorMessageTextColor;
break;
default:
self.statusLabel1.textColor = kDarkThemeTextColor;
self.statusLabel2.textColor = kDarkThemeTextColor;
self.finishedLabel.textColor = kDarkThemeTextColor;
break;
}
self.activityIndicator.activityIndicatorViewStyle = kDarkThemeActivityIndicatorViewStyle;
if ([self.activityIndicator respondsToSelector:@selector(setColor:)]) {
[self.activityIndicator setColor:kDarkThemeTextColor];
}
self.detailView.backgroundColor = kDarkThemeDetailViewBackgroundColor;
self.detailView.layer.borderColor = [kDarkThemeDetailViewBorderColor CGColor];
self.historyTableView.separatorColor = kDarkThemeDetailViewBorderColor;
self.detailTextView.textColor = kDarkThemeHistoryTextColor;
self.progressView.backgroundColor = [UIColor clearColor];
}
// override with custom color
if (self.customTextColor) {
self.statusLabel1.textColor = self.customTextColor;
self.statusLabel2.textColor = self.customTextColor;
self.finishedLabel.textColor = self.customTextColor;
self.activityIndicator.color = self.customTextColor;
}
}
- (void)updateUIForMessageType:(MTMessageType)messageType duration:(NSTimeInterval)duration {
// set properties depending on message-type
switch (messageType) {
case MTMessageTypeActivity:
// will not call hide after delay
self.hideInProgress = NO;
// show activity indicator, hide finished-label
self.finishedLabel.hidden = YES;
self.activityIndicator.hidden = self.hidesActivity;
// start activity indicator
if (!self.hidesActivity) {
[self.activityIndicator startAnimating];
}
break;
case MTMessageTypeFinish:
// will call hide after delay
self.hideInProgress = YES;
// show finished-label, hide acitvity indicator
self.finishedLabel.hidden = self.hidesActivity;
self.activityIndicator.hidden = YES;
// stop activity indicator
[self.activityIndicator stopAnimating];
// update font and text
self.finishedLabel.font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:kFinishedFontSize];
self.finishedLabel.text = kFinishedText;
self.progress = 1.0;
break;
case MTMessageTypeError:
// will call hide after delay
self.hideInProgress = YES;
// show finished-label, hide activity indicator
self.finishedLabel.hidden = self.hidesActivity;
self.activityIndicator.hidden = YES;
// stop activity indicator
[self.activityIndicator stopAnimating];
// update font and text
self.finishedLabel.font = [UIFont boldSystemFontOfSize:kErrorFontSize];
self.finishedLabel.text = kErrorText;
self.progress = 1.0;
break;
}
// if a duration is specified, hide after given duration
if (duration > 0.) {
// hide after duration
[self performSelector:@selector(hide) withObject:nil afterDelay:duration];
// clear history after duration
[self performSelector:@selector(clearHistory) withObject:nil afterDelay:duration];
}
}
- (void)callDelegateWithNewMessage:(NSString *)newMessage {
if ([self.delegate respondsToSelector:@selector(statusBarOverlayDidSwitchFromOldMessage:toNewMessage:)]) {
NSString *oldMessage = nil;
if (self.messageHistory.count > 0) {
oldMessage = [self.messageHistory lastObject];
}
[self.delegate statusBarOverlayDidSwitchFromOldMessage:oldMessage
toNewMessage:newMessage];
}
}
- (void)updateDetailTextViewHeight {
CGRect f = self.detailTextView.frame;
f.size.height = self.detailTextView.contentSize.height;
self.detailTextView.frame = f;
}
- (void)updateProgressViewSizeForLabel:(UILabel *)label {
if (self.progress < 1.) {
CGSize size = [label sizeThatFits:label.frame.size];
CGFloat width = size.width * (float) (1. - self.progress);
CGFloat x = label.center.x + size.width / 2.f - width;
// if we werent able to determine a size, do nothing
if (size.width == 0.f) {
return;
}
// progressView always covers only the visible portion of the text
// it "shrinks" to the right with increased progress to reveal more
// text under it
self.progressView.hidden = NO;
//[UIView animateWithDuration:self.progress > 0.0 ? kUpdateProgressViewDuration : 0.0
// animations:^{
self.progressView.frame = CGRectMake(x, self.progressView.frame.origin.y,
self.backgroundView.frame.size.width - x, self.progressView.frame.size.height);
// }];
} else {
self.progressView.hidden = YES;
}
}
- (CGRect)backgroundViewFrameForStatusBarInterfaceOrientation {
UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
return (UIInterfaceOrientationIsLandscape(interfaceOrientation) ?
CGRectMake(0, 0, kScreenHeight, kStatusBarHeight) :
CGRectMake(0, 0, kScreenWidth, kStatusBarHeight));
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark History Tracking
////////////////////////////////////////////////////////////////////////
- (BOOL)isHistoryEnabled {
return self.detailViewMode == MTDetailViewModeHistory;
}
- (void)setHistoryEnabled:(BOOL)historyEnabled {
if (historyEnabled) {
self.detailViewMode = MTDetailViewModeHistory;
} else {
self.detailViewMode = MTDetailViewModeCustom;
}
self.historyTableView.hidden = !historyEnabled;
}
- (void)addMessageToHistory:(NSString *)message {
if (message != nil
&& [message stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]].length > 0) {
// add message to history-array
[self.messageHistory addObject:message];
if (self.historyEnabled) {
NSIndexPath *newHistoryMessageIndexPath = [NSIndexPath indexPathForRow:self.messageHistory.count - 1 inSection:0];
[self setDetailViewHidden:self.detailViewHidden animated:YES];
// update history table-view
[self.historyTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newHistoryMessageIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.historyTableView scrollToRowAtIndexPath:newHistoryMessageIndexPath
atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
}
- (void)clearHistory {
[self.messageHistory removeAllObjects];
[self.historyTableView reloadData];
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Custom Hide Methods
////////////////////////////////////////////////////////////////////////
// used for performSelector:withObject
- (void)setHiddenUsingAlpha:(BOOL)hidden {
[self setHidden:hidden useAlpha:YES];
}
- (void)setHidden:(BOOL)hidden useAlpha:(BOOL)useAlpha {
if (useAlpha) {
self.alpha = hidden ? 0.f : 1.f;
} else {
self.hidden = hidden;
}
}
- (BOOL)isReallyHidden {
return self.alpha == 0.f || self.hidden;
}
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Singleton Definitions
////////////////////////////////////////////////////////////////////////
+ (MTStatusBarOverlay *)sharedInstance {
static dispatch_once_t pred;
__strong static MTStatusBarOverlay *sharedOverlay = nil;
dispatch_once(&pred, ^{
sharedOverlay = [[MTStatusBarOverlay alloc] init];
});
return sharedOverlay;
}
+ (MTStatusBarOverlay *)sharedOverlay {
return [self sharedInstance];
}
@end
////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark other
////////////////////////////////////////////////////////////////////////
unsigned int Silver_Base_Shrinked_2x_png_len = 1030;
void mt_dispatch_sync_on_main_thread(dispatch_block_t block) {
if ([NSThread isMainThread]) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment