// // KGModal.m // KGModal // // Created by David Keegan on 10/5/12. // Copyright (c) 2012 David Keegan. All rights reserved. // #import "KGModal.h" #import CGFloat const kFadeInAnimationDuration = 0.3; CGFloat const kTransformPart1AnimationDuration = 0.2; CGFloat const kTransformPart2AnimationDuration = 0.1; CGFloat const kDefaultCloseButtonPadding = 17.0; NSString *const KGModalGradientViewTapped = @"KGModalGradientViewTapped"; NSString *const KGModalWillShowNotification = @"KGModalWillShowNotification"; NSString *const KGModalDidShowNotification = @"KGModalDidShowNotification"; NSString *const KGModalWillHideNotification = @"KGModalWillHideNotification"; NSString *const KGModalDidHideNotification = @"KGModalDidHideNotification"; @interface KGModalGradientView : UIView @end @interface KGModalContainerView : UIView @property (weak, nonatomic) CALayer *styleLayer; @property (strong, nonatomic) UIColor *modalBackgroundColor; @end @interface KGModalCloseButton : UIButton @end @interface KGModalViewController : UIViewController @property (weak, nonatomic) KGModalGradientView *styleView; @end @interface KGModal() @property (strong, nonatomic) TPWindow *window; @property (strong, nonatomic) UIViewController *contentViewController; @property (weak, nonatomic) KGModalViewController *viewController; @property (weak, nonatomic) KGModalContainerView *containerView; @property (weak, nonatomic) KGModalCloseButton *closeButton; @property (weak, nonatomic) UIView *contentView; @property (copy) void (^dismissHandler)(); @end @implementation KGModal @synthesize tapOutsideToDismiss = _tapOutsideToDismiss; - (void)setTapOutsideToDismiss:(BOOL)tapOutside { _tapOutsideToDismiss = tapOutside; if (tapOutside) { self.tapHandler = nil; } } + (instancetype)sharedInstance{ static id sharedInstance; static dispatch_once_t once; dispatch_once(&once, ^{ sharedInstance = [[[self class] alloc] init]; }); return sharedInstance; } - (instancetype)init{ if(!(self = [super init])){ return nil; } self.shouldRotate = YES; self.tapOutsideToDismiss = YES; self.animateWhenDismissed = YES; self.closeButtonType = KGModalCloseButtonTypeLeft; self.modalBackgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; return self; } -(void)setCloseButtonType:(KGModalCloseButtonType)closeButtonType { _closeButtonType = closeButtonType; if(closeButtonType == KGModalCloseButtonTypeNone){ [self.closeButton setHidden:YES]; }else{ [self.closeButton setHidden:NO]; CGRect closeFrame = self.closeButton.frame; if(closeButtonType == KGModalCloseButtonTypeRight){ closeFrame.origin.x = round(CGRectGetWidth(self.containerView.frame)-kDefaultCloseButtonPadding-CGRectGetWidth(closeFrame)/2); }else{ closeFrame.origin.x = 0; } self.closeButton.frame = closeFrame; } } - (void)showWithContentView:(UIView *)contentView completion:(void (^)(void))completion{ self.dismissHandler = nil; self.tapHandler = nil; [self showWithContentView:contentView andAnimated:YES completion:completion]; } - (void)showWithContentViewController:(UIViewController *)contentViewController completion:(void (^)(void))completion{ self.dismissHandler = nil; self.tapHandler = nil; [self showWithContentViewController:contentViewController andAnimated:YES completion:completion]; } - (void)showWithContentViewController:(UIViewController *)contentViewController andAnimated:(BOOL)animated completion:(void (^)(void))completion{ self.dismissHandler = nil; self.contentViewController = contentViewController; self.tapHandler = nil; [self showWithContentView:contentViewController.view andAnimated:YES completion:(void (^)(void))completion]; } - (void)showWithContentViewController:(UIViewController *)contentViewController andAnimated:(BOOL)animated completion:(void (^)(void))completion dismiss:(void (^)(void))dissmiss tapHandler:(void (^)(void))tapHandler{ self.dismissHandler = dissmiss; self.contentViewController = contentViewController; self.tapHandler = tapHandler; [self showWithContentView:contentViewController.view andAnimated:YES completion:(void (^)(void))completion]; } - (void)showWithContentView:(UIView *)contentView andAnimated:(BOOL)animated completion:(void (^)(void))completion{ self.window = [[TPWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; self.window.opaque = NO; KGModalViewController *viewController = [[KGModalViewController alloc] init]; self.window.rootViewController = viewController; self.viewController = viewController; CGFloat padding = 17; CGRect containerViewRect = CGRectInset(contentView.bounds, -padding, -padding); containerViewRect.origin.x = containerViewRect.origin.y = 0; containerViewRect.origin.x = round(CGRectGetMidX(self.window.bounds)-CGRectGetMidX(containerViewRect)); containerViewRect.origin.y = round(CGRectGetMidY(self.window.bounds)-CGRectGetMidY(containerViewRect)); KGModalContainerView *containerView = [[KGModalContainerView alloc] initWithFrame:containerViewRect]; containerView.modalBackgroundColor = self.modalBackgroundColor; containerView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin| UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin; containerView.layer.rasterizationScale = [[UIScreen mainScreen] scale]; contentView.frame = (CGRect){padding, padding, contentView.bounds.size}; [containerView addSubview:contentView]; [viewController.view addSubview:containerView]; self.containerView = containerView; KGModalCloseButton *closeButton = [[KGModalCloseButton alloc] init]; if(self.closeButtonType == KGModalCloseButtonTypeRight){ CGRect closeFrame = closeButton.frame; closeFrame.origin.x = CGRectGetWidth(containerView.bounds)-CGRectGetWidth(closeFrame); closeButton.frame = closeFrame; } [closeButton addTarget:self action:@selector(closeAction:) forControlEvents:UIControlEventTouchUpInside]; [containerView addSubview:closeButton]; self.closeButton = closeButton; // Force adjust visibility and placing [self setCloseButtonType:self.closeButtonType]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tapCloseAction:) name:KGModalGradientViewTapped object:nil]; // The window has to be un-hidden on the main thread // This will cause the window to display dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:KGModalWillShowNotification object:self]; [self.window makeKeyAndVisible]; if ([[TPUserDefault instance].isNightMode intValue] > 0) { [self.window setMaskAfterOpenNightMode]; } if(animated){ viewController.styleView.alpha = 0; [UIView animateWithDuration:kFadeInAnimationDuration animations:^{ viewController.styleView.alpha = 1; }]; containerView.alpha = 0; containerView.layer.shouldRasterize = YES; containerView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.4, 0.4); [UIView animateWithDuration:kTransformPart1AnimationDuration animations:^{ containerView.alpha = 1; containerView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); } completion:^(BOOL finished) { containerView.layer.shouldRasterize = NO; [[NSNotificationCenter defaultCenter] postNotificationName:KGModalDidShowNotification object:self]; if (completion) { completion(); } }]; } }); } - (void)closeAction:(id)sender{ [self hideAnimated:self.animateWhenDismissed]; } - (void)tapCloseAction:(id)sender{ if(self.tapOutsideToDismiss){ [self hideAnimated:self.animateWhenDismissed]; }else { if (self.tapHandler) { self.tapHandler(); } } } - (void)hide{ [self hideAnimated:YES]; } - (void)hideWithCompletionBlock:(void(^)())completion{ [self hideAnimated:YES withCompletionBlock:completion]; } - (void)hideAnimated:(BOOL)animated{ [self hideAnimated:animated withCompletionBlock:nil]; } - (void)hideAnimated:(BOOL)animated withCompletionBlock:(void(^)())completion{ if (!self.window) { if (completion) { completion(); } return; } if(!animated){ [self cleanup]; if (completion) { completion(); } [[NSNotificationCenter defaultCenter] postNotificationName:KGModalDidHideNotification object:self]; return; } dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:KGModalWillHideNotification object:self]; [UIView animateWithDuration:kFadeInAnimationDuration animations:^{ self.viewController.styleView.alpha = 0; }]; self.containerView.layer.shouldRasterize = YES; [UIView animateWithDuration:kTransformPart1AnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ self.containerView.alpha = 0; self.containerView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.4, 0.4); } completion:^(BOOL finished2){ [self cleanup]; if (self.dismissHandler) { self.dismissHandler(); } if(completion){ completion(); } [[NSNotificationCenter defaultCenter] postNotificationName:KGModalDidHideNotification object:self]; }]; }); } - (void)cleanup{ [self.window setNoNightModeMask]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [self.containerView removeFromSuperview]; [[[[UIApplication sharedApplication] delegate] window] makeKeyAndVisible]; [self.window removeFromSuperview]; self.contentViewController = nil; self.window = nil; } - (void)setModalBackgroundColor:(UIColor *)modalBackgroundColor{ if(_modalBackgroundColor != modalBackgroundColor){ _modalBackgroundColor = modalBackgroundColor; self.containerView.modalBackgroundColor = modalBackgroundColor; } } - (void)dealloc{ [self cleanup]; } @end @implementation KGModalViewController - (void)loadView{ self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; } - (void)viewDidLoad{ [super viewDidLoad]; self.view.backgroundColor = [UIColor clearColor]; self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; KGModalGradientView *styleView = [[KGModalGradientView alloc] initWithFrame:self.view.bounds]; styleView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; styleView.opaque = NO; [self.view addSubview:styleView]; self.styleView = styleView; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{ return [[KGModal sharedInstance] shouldRotate]; } - (BOOL)shouldAutorotate{ return [[KGModal sharedInstance] shouldRotate]; } @end @implementation KGModalContainerView - (instancetype)initWithFrame:(CGRect)frame{ if(!(self = [super initWithFrame:frame])){ return nil; } // CALayer *styleLayer = [[CALayer alloc] init]; // styleLayer.cornerRadius = 4; // styleLayer.shadowColor= [[UIColor blackColor] CGColor]; // styleLayer.shadowOffset = CGSizeMake(0, 0); // styleLayer.shadowOpacity = 0.5; // styleLayer.borderWidth = 1; // styleLayer.borderColor = [[UIColor whiteColor] CGColor]; // styleLayer.frame = CGRectInset(self.bounds, 12, 12); // [self.layer addSublayer:styleLayer]; // self.styleLayer = styleLayer; self.clipsToBounds = YES; return self; } - (void)setModalBackgroundColor:(UIColor *)modalBackgroundColor{ if(_modalBackgroundColor != modalBackgroundColor){ _modalBackgroundColor = modalBackgroundColor; self.styleLayer.backgroundColor = [modalBackgroundColor CGColor]; } } @end @implementation KGModalGradientView - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ [[NSNotificationCenter defaultCenter] postNotificationName:KGModalGradientViewTapped object:nil]; } - (void)drawRect:(CGRect)rect{ CGContextRef context = UIGraphicsGetCurrentContext(); if ([[TPUserDefault instance].isNightMode intValue] > 0) { UIColor *maskColor = [UIColor colorWithWhite:0.25 alpha:0.55]; [maskColor set]; CGContextFillRect(context, self.bounds); }else { if([[KGModal sharedInstance] backgroundDisplayStyle] == KGModalBackgroundDisplayStyleSolid){ [[UIColor colorWithWhite:0 alpha:0.55] set]; CGContextFillRect(context, self.bounds); }else{ CGContextSaveGState(context); size_t gradLocationsNum = 2; CGFloat gradLocations[2] = {0.0f, 1.0f}; CGFloat gradColors[8] = {0, 0, 0, 0.3, 0, 0, 0, 0.8}; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum); CGColorSpaceRelease(colorSpace), colorSpace = NULL; CGPoint gradCenter= CGPointMake(round(CGRectGetMidX(self.bounds)), round(CGRectGetMidY(self.bounds))); CGFloat gradRadius = MAX(CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)); CGContextDrawRadialGradient(context, gradient, gradCenter, 0, gradCenter, gradRadius, kCGGradientDrawsAfterEndLocation); CGGradientRelease(gradient), gradient = NULL; CGContextRestoreGState(context); } } } @end @implementation KGModalCloseButton - (instancetype)init{ if(!(self = [super initWithFrame:(CGRect){0, 0, 32, 32}])){ return nil; } static UIImage *closeButtonImage; static dispatch_once_t once; dispatch_once(&once, ^{ closeButtonImage = [self closeButtonImage]; }); [self setBackgroundImage:closeButtonImage forState:UIControlStateNormal]; return self; } - (UIImage *)closeButtonImage{ UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0); //// General Declarations CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = UIGraphicsGetCurrentContext(); //// Color Declarations UIColor *topGradient = [UIColor colorWithRed:0.21 green:0.21 blue:0.21 alpha:0.9]; UIColor *bottomGradient = [UIColor colorWithRed:0.03 green:0.03 blue:0.03 alpha:0.9]; //// Gradient Declarations NSArray *gradientColors = @[(id)topGradient.CGColor, (id)bottomGradient.CGColor]; CGFloat gradientLocations[] = {0, 1}; CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)gradientColors, gradientLocations); //// Shadow Declarations CGColorRef shadow = [UIColor blackColor].CGColor; CGSize shadowOffset = CGSizeMake(0, 1); CGFloat shadowBlurRadius = 3; CGColorRef shadow2 = [UIColor blackColor].CGColor; CGSize shadow2Offset = CGSizeMake(0, 1); CGFloat shadow2BlurRadius = 0; //// Oval Drawing UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(4, 3, 24, 24)]; CGContextSaveGState(context); [ovalPath addClip]; CGContextDrawLinearGradient(context, gradient, CGPointMake(16, 3), CGPointMake(16, 27), 0); CGContextRestoreGState(context); CGContextSaveGState(context); CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, shadow); [[UIColor whiteColor] setStroke]; ovalPath.lineWidth = 2; [ovalPath stroke]; CGContextRestoreGState(context); //// Bezier Drawing UIBezierPath *bezierPath = [UIBezierPath bezierPath]; [bezierPath moveToPoint:CGPointMake(22.36, 11.46)]; [bezierPath addLineToPoint:CGPointMake(18.83, 15)]; [bezierPath addLineToPoint:CGPointMake(22.36, 18.54)]; [bezierPath addLineToPoint:CGPointMake(19.54, 21.36)]; [bezierPath addLineToPoint:CGPointMake(16, 17.83)]; [bezierPath addLineToPoint:CGPointMake(12.46, 21.36)]; [bezierPath addLineToPoint:CGPointMake(9.64, 18.54)]; [bezierPath addLineToPoint:CGPointMake(13.17, 15)]; [bezierPath addLineToPoint:CGPointMake(9.64, 11.46)]; [bezierPath addLineToPoint:CGPointMake(12.46, 8.64)]; [bezierPath addLineToPoint:CGPointMake(16, 12.17)]; [bezierPath addLineToPoint:CGPointMake(19.54, 8.64)]; [bezierPath addLineToPoint:CGPointMake(22.36, 11.46)]; [bezierPath closePath]; CGContextSaveGState(context); CGContextSetShadowWithColor(context, shadow2Offset, shadow2BlurRadius, shadow2); [[UIColor whiteColor] setFill]; [bezierPath fill]; CGContextRestoreGState(context); //// Cleanup CGGradientRelease(gradient); CGColorSpaceRelease(colorSpace); UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } @end