// // MPNotificationView.m // Moped // // Created by Engin Kurutepe on 1/2/13. // Copyright (c) 2013 Moped Inc. All rights reserved. // #import "MPNotificationView.h" #import "OBGradientView.h" #define kMPNotificationHeight 40.0f #define kMPNotificationIPadWidth 480.0f #define RADIANS(deg) ((deg) * M_PI / 180.0f) static CGRect notificationRect() { if (UIDeviceOrientationIsLandscape([[UIDevice currentDevice] orientation])) { return CGRectMake(0.0f, 0.0f, [UIScreen mainScreen].bounds.size.height, kMPNotificationHeight); } return CGRectMake(0.0f, 0.0f, [UIScreen mainScreen].bounds.size.width, kMPNotificationHeight); } NSString *kMPNotificationViewTapReceivedNotification = @"kMPNotificationViewTapReceivedNotification"; #pragma mark MPNotificationWindow @interface MPNotificationWindow : UIWindow @property (nonatomic, strong) NSMutableArray *notificationQueue; @property (nonatomic, strong) UIView *currentNotification; @end @implementation MPNotificationWindow - (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } - (id) initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.windowLevel = UIWindowLevelStatusBar + 1; self.backgroundColor = [UIColor clearColor]; _notificationQueue = [[NSMutableArray alloc] initWithCapacity:4]; UIView *topHalfBlackView = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetMinX(frame), CGRectGetMinY(frame), CGRectGetWidth(frame), 0.5 * CGRectGetHeight(frame))]; topHalfBlackView.backgroundColor = [UIColor blackColor]; topHalfBlackView.layer.zPosition = -100; topHalfBlackView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self addSubview:topHalfBlackView]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willRotateScreen:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil]; [self rotateStatusBarWithFrame:frame]; } return self; } - (void) willRotateScreen:(NSNotification *)notification { CGRect notificationBarFrame = notificationRect(); if (self.hidden) { [self rotateStatusBarWithFrame:notificationBarFrame]; } else { [self rotateStatusBarAnimatedWithFrame:notificationBarFrame]; } } - (void) rotateStatusBarAnimatedWithFrame:(CGRect)frame { CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration; [UIView animateWithDuration:duration animations:^{ self.alpha = 0; } completion:^(BOOL finished) { [self rotateStatusBarWithFrame:frame]; [UIView animateWithDuration:duration animations:^{ self.alpha = 1; }]; }]; } - (void) rotateStatusBarWithFrame:(CGRect)frame { BOOL isPortrait = (frame.size.width == [UIScreen mainScreen].bounds.size.width); if (isPortrait) { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { frame.size.width = kMPNotificationIPadWidth; } if ([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortraitUpsideDown) { frame.origin.y = [UIScreen mainScreen].bounds.size.height - kMPNotificationHeight; self.transform = CGAffineTransformMakeRotation(RADIANS(180.0f)); } else { self.transform = CGAffineTransformIdentity; } } else { frame.size.height = frame.size.width; frame.size.width = kMPNotificationHeight; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { frame.size.height = kMPNotificationIPadWidth; } if ([[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeLeft) { frame.origin.x = [UIScreen mainScreen].bounds.size.width - frame.size.width; self.transform = CGAffineTransformMakeRotation(RADIANS(90.0f)); } else { self.transform = CGAffineTransformMakeRotation(RADIANS(-90.0f)); } } self.frame = frame; CGPoint center = self.center; if (isPortrait) { center.x = CGRectGetMidX([UIScreen mainScreen].bounds); } else { center.y = CGRectGetMidY([UIScreen mainScreen].bounds); } self.center = center; } @end static MPNotificationWindow * __notificationWindow = nil; static CGFloat const __imagePadding = 8.0f; #pragma mark - #pragma mark MPNotificationView @interface MPNotificationView () @property (nonatomic, strong) OBGradientView * contentView; @property (nonatomic, copy) MPNotificationSimpleAction tapBlock; @property (nonatomic, strong) UITapGestureRecognizer *tapGestureRecognizer; + (void) showNextNotification; + (UIImage*) screenImageWithRect:(CGRect)rect; @end @implementation MPNotificationView - (void) dealloc { _delegate = nil; [self removeGestureRecognizer:_tapGestureRecognizer]; [super dealloc]; } - (id) initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { CGFloat notificationWidth = notificationRect().size.width; self.autoresizingMask = UIViewAutoresizingFlexibleWidth; _contentView = [[OBGradientView alloc] initWithFrame:self.bounds]; _contentView.colors = @[(id)[[UIColor colorWithWhite:0.99f alpha:1.0f] CGColor], (id)[[UIColor colorWithWhite:0.9f alpha:1.0f] CGColor]]; _contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth; _contentView.layer.cornerRadius = 0.0f; _contentView.clipsToBounds = YES; [self addSubview:_contentView]; _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(6, 6, 28, 28)]; _imageView.contentMode = UIViewContentModeScaleAspectFill; _imageView.layer.cornerRadius = 4.0f; _imageView.clipsToBounds = YES; [self addSubview:_imageView]; UIFont *textFont = [UIFont boldSystemFontOfSize:14.0f]; CGRect textFrame = CGRectMake(__imagePadding + CGRectGetMaxX(_imageView.frame), 2, notificationWidth - __imagePadding * 2 - CGRectGetMaxX(_imageView.frame), textFont.lineHeight); _textLabel = [[UILabel alloc] initWithFrame:textFrame]; _textLabel.font = textFont; _textLabel.numberOfLines = 1; _textLabel.textAlignment = NSTextAlignmentLeft; _textLabel.lineBreakMode = UILineBreakModeTailTruncation; _textLabel.backgroundColor = [UIColor clearColor]; [_contentView addSubview:_textLabel]; UIFont *detailFont = [UIFont systemFontOfSize:13.0f]; CGRect detailFrame = CGRectMake(CGRectGetMinX(textFrame), CGRectGetMaxY(textFrame), notificationWidth - __imagePadding * 2 - CGRectGetMaxX(_imageView.frame), detailFont.lineHeight); _detailTextLabel = [[UILabel alloc] initWithFrame:detailFrame]; _detailTextLabel.font = detailFont; _detailTextLabel.numberOfLines = 1; _detailTextLabel.textAlignment = NSTextAlignmentLeft; _detailTextLabel.lineBreakMode = UILineBreakModeTailTruncation; _detailTextLabel.backgroundColor = [UIColor clearColor]; [_contentView addSubview:_detailTextLabel]; UIView *bottomLine = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetMinX(frame), CGRectGetHeight(frame) - 1.0f, CGRectGetWidth(frame), 1.0f)]; bottomLine.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0]; [_contentView addSubview:bottomLine]; } return self; } + (MPNotificationView *) notifyWithText:(NSString*)text andDetail:(NSString*)detail { return [self notifyWithText:text detail:detail andDuration:2.0f]; } + (MPNotificationView *) notifyWithText:(NSString*)text detail:(NSString*)detail andDuration:(NSTimeInterval)duration { return [self notifyWithText:text detail:detail image:nil andDuration:duration]; } + (MPNotificationView *) notifyWithText:(NSString*)text detail:(NSString*)detail image:(UIImage*)image andDuration:(NSTimeInterval)duration { return [self notifyWithText:text detail:detail image:image duration:duration andTouchBlock:nil]; } + (MPNotificationView *) notifyWithText:(NSString*)text detail:(NSString*)detail duration:(NSTimeInterval)duration andTouchBlock:(MPNotificationSimpleAction)block { return [self notifyWithText:text detail:detail image:nil duration:duration andTouchBlock:block]; } + (MPNotificationView *) notifyWithText:(NSString*)text detail:(NSString*)detail andTouchBlock:(MPNotificationSimpleAction)block { return [self notifyWithText:text detail:detail image:nil duration:2.0 andTouchBlock:block]; } + (MPNotificationView *) notifyWithText:(NSString*)text detail:(NSString*)detail image:(UIImage*)image duration:(NSTimeInterval)duration andTouchBlock:(MPNotificationSimpleAction)block { if (__notificationWindow == nil) { __notificationWindow = [[MPNotificationWindow alloc] initWithFrame:notificationRect()]; __notificationWindow.hidden = NO; } MPNotificationView * notification = [[MPNotificationView alloc] initWithFrame:__notificationWindow.bounds]; notification.textLabel.text = text; notification.detailTextLabel.text = detail; notification.imageView.image = image; notification.duration = duration; notification.tapBlock = block; UITapGestureRecognizer *gr = [[UITapGestureRecognizer alloc] initWithTarget:notification action:@selector(handleTap:)]; notification.tapGestureRecognizer = gr; [notification addGestureRecognizer:gr]; [__notificationWindow.notificationQueue addObject:notification]; if (__notificationWindow.currentNotification == nil) { [self showNextNotification]; } return notification; } - (void) handleTap:(UITapGestureRecognizer *)gestureRecognizer { if (_tapBlock != nil) { _tapBlock(self); } if ([_delegate respondsToSelector:@selector(tapReceivedForNotificationView:)]) { [_delegate didTapOnNotificationView:self]; } [[NSNotificationCenter defaultCenter] postNotificationName:kMPNotificationViewTapReceivedNotification object:self]; [NSObject cancelPreviousPerformRequestsWithTarget:[self class] selector:@selector(showNextNotification) object:nil]; [MPNotificationView showNextNotification]; } + (void) showNextNotification { UIView *viewToRotateOut = nil; UIImage *screenshot = [self screenImageWithRect:__notificationWindow.frame]; if (__notificationWindow.currentNotification) { viewToRotateOut = __notificationWindow.currentNotification; } else { viewToRotateOut = [[UIImageView alloc] initWithFrame:__notificationWindow.bounds]; ((UIImageView *)viewToRotateOut).image = screenshot; [__notificationWindow addSubview:viewToRotateOut]; __notificationWindow.hidden = NO; } UIView *viewToRotateIn = nil; if ([__notificationWindow.notificationQueue count] > 0) { viewToRotateIn = __notificationWindow.notificationQueue[0]; } else { viewToRotateIn = [[UIImageView alloc] initWithFrame:__notificationWindow.bounds]; ((UIImageView *)viewToRotateIn).image = screenshot; } viewToRotateIn.layer.anchorPointZ = 11.547f; viewToRotateIn.layer.doubleSided = NO; viewToRotateIn.layer.zPosition = 2; CATransform3D viewInStartTransform = CATransform3DMakeRotation(RADIANS(-120), 1.0, 0.0, 0.0); viewInStartTransform.m34 = -1.0 / 200.0; viewToRotateOut.layer.anchorPointZ = 11.547f; viewToRotateOut.layer.doubleSided = NO; viewToRotateOut.layer.zPosition = 2; CATransform3D viewOutEndTransform = CATransform3DMakeRotation(RADIANS(120), 1.0, 0.0, 0.0); viewOutEndTransform.m34 = -1.0 / 200.0; [__notificationWindow addSubview:viewToRotateIn]; __notificationWindow.backgroundColor = [UIColor blackColor]; viewToRotateIn.layer.transform = viewInStartTransform; if ([viewToRotateIn isKindOfClass:[MPNotificationView class]] ) { UIImageView *bgImage = [[UIImageView alloc] initWithFrame:__notificationWindow.bounds]; bgImage.image = screenshot; [viewToRotateIn addSubview:bgImage]; [viewToRotateIn sendSubviewToBack:bgImage]; __notificationWindow.currentNotification = viewToRotateIn; } [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ viewToRotateIn.layer.transform = CATransform3DIdentity; viewToRotateOut.layer.transform = viewOutEndTransform; } completion:^(BOOL finished) { [viewToRotateOut removeFromSuperview]; [__notificationWindow.notificationQueue removeObject:viewToRotateOut]; if ([viewToRotateIn isKindOfClass:[MPNotificationView class]]) { MPNotificationView *notification = (MPNotificationView*)viewToRotateIn; [self performSelector:@selector(showNextNotification) withObject:nil afterDelay:notification.duration]; __notificationWindow.currentNotification = notification; [__notificationWindow.notificationQueue removeObject:notification]; } else { [viewToRotateIn removeFromSuperview]; __notificationWindow.hidden = YES; __notificationWindow.currentNotification = nil; } __notificationWindow.backgroundColor = [UIColor clearColor]; }]; } + (UIImage *) screenImageWithRect:(CGRect)rect { CALayer *layer = [[UIApplication sharedApplication] keyWindow].layer; CGFloat scale = [UIScreen mainScreen].scale; UIGraphicsBeginImageContextWithOptions(layer.frame.size, NO, scale); [layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); rect = CGRectMake(rect.origin.x * scale, rect.origin.y * scale , rect.size.width * scale, rect.size.height * scale); CGImageRef imageRef = CGImageCreateWithImageInRect([screenshot CGImage], rect); UIImage *croppedScreenshot = [UIImage imageWithCGImage:imageRef scale:screenshot.scale orientation:screenshot.imageOrientation]; CGImageRelease(imageRef); UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; UIImageOrientation imageOrientation = UIImageOrientationUp; switch (orientation) { case UIDeviceOrientationPortraitUpsideDown: imageOrientation = UIImageOrientationDown; break; case UIDeviceOrientationLandscapeRight: imageOrientation = UIImageOrientationRight; break; case UIDeviceOrientationLandscapeLeft: imageOrientation = UIImageOrientationLeft; break; default: break; } return [[UIImage alloc] initWithCGImage:croppedScreenshot.CGImage scale:croppedScreenshot.scale orientation:imageOrientation]; } @end