diff --git a/KNSemiModalViewController.podspec b/KNSemiModalViewController.podspec index d44a8c7..9f20363 100644 --- a/KNSemiModalViewController.podspec +++ b/KNSemiModalViewController.podspec @@ -26,7 +26,7 @@ Pod::Spec.new do |s| s.summary = 'Replica of the semi-modal view with pushed-back stacked animation found in the beautiful Park Guides by National Geographic app. Custom background view option was added in this fork.' s.homepage = 'https://github.com/opedge/KNSemiModalViewController' s.author = { 'Kent Nguyen' => 'nguyen.dmz@gmail.com', 'Oleg Poyaganov' => 'opedge@gmail.com' } - s.source = { :git => 'https://github.com/opedge/KNSemiModalViewController.git', :tag => '0.4' } + s.source = { :git => 'https://github.com/kentnguyen/KNSemiModalViewController.git', :tag => '0.4' } s.platform = :ios s.source_files = 'Source' diff --git a/Source/UIViewController+KNSemiModal.m b/Source/UIViewController+KNSemiModal.m index 26421af..ed0da90 100755 --- a/Source/UIViewController+KNSemiModal.m +++ b/Source/UIViewController+KNSemiModal.m @@ -1,3 +1,4 @@ + // // KNSemiModalViewController.m // KNSemiModalViewController @@ -11,13 +12,13 @@ #import const struct KNSemiModalOptionKeys KNSemiModalOptionKeys = { - .traverseParentHierarchy = @"KNSemiModalOptionTraverseParentHierarchy", - .pushParentBack = @"KNSemiModalOptionPushParentBack", - .animationDuration = @"KNSemiModalOptionAnimationDuration", - .parentAlpha = @"KNSemiModalOptionParentAlpha", + .traverseParentHierarchy = @"KNSemiModalOptionTraverseParentHierarchy", + .pushParentBack = @"KNSemiModalOptionPushParentBack", + .animationDuration = @"KNSemiModalOptionAnimationDuration", + .parentAlpha = @"KNSemiModalOptionParentAlpha", .parentScale = @"KNSemiModalOptionParentScale", - .shadowOpacity = @"KNSemiModalOptionShadowOpacity", - .transitionStyle = @"KNSemiModalTransitionStyle", + .shadowOpacity = @"KNSemiModalOptionShadowOpacity", + .transitionStyle = @"KNSemiModalTransitionStyle", .disableCancel = @"KNSemiModalOptionDisableCancel", .backgroundView = @"KNSemiModelOptionBackgroundView", }; @@ -37,16 +38,19 @@ -(CAAnimationGroup*)animationGroupForward:(BOOL)_forward; @implementation UIViewController (KNSemiModalInternal) +UIDeviceOrientation orientationState; //set in -presentSemiView + -(UIViewController*)kn_parentTargetViewController { - UIViewController * target = self; - if ([[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.traverseParentHierarchy] boolValue]) { - // cover UINav & UITabbar as well - while (target.parentViewController != nil) { - target = target.parentViewController; - } - } - return target; + UIViewController * target = self; + if ([[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.traverseParentHierarchy] boolValue]) { + // cover UINav & UITabbar as well + while (target.parentViewController != nil) { + target = target.parentViewController; + } + } + return target; } + -(UIView*)parentTarget { return [self kn_parentTargetViewController].view; } @@ -54,16 +58,16 @@ -(UIView*)parentTarget { #pragma mark Options and defaults -(void)kn_registerDefaultsAndOptions:(NSDictionary*)options { - [self ym_registerOptions:options defaults:@{ - KNSemiModalOptionKeys.traverseParentHierarchy : @(YES), - KNSemiModalOptionKeys.pushParentBack : @(YES), - KNSemiModalOptionKeys.animationDuration : @(0.5), - KNSemiModalOptionKeys.parentAlpha : @(0.5), - KNSemiModalOptionKeys.parentScale : @(0.8), - KNSemiModalOptionKeys.shadowOpacity : @(0.8), - KNSemiModalOptionKeys.transitionStyle : @(KNSemiModalTransitionStyleSlideUp), - KNSemiModalOptionKeys.disableCancel : @(NO), - }]; + [self ym_registerOptions:options defaults:@{ + KNSemiModalOptionKeys.traverseParentHierarchy : @(YES), + KNSemiModalOptionKeys.pushParentBack : @(YES), + KNSemiModalOptionKeys.animationDuration : @(0.5), + KNSemiModalOptionKeys.parentAlpha : @(0.5), + KNSemiModalOptionKeys.parentScale : @(0.8), + KNSemiModalOptionKeys.shadowOpacity : @(0.8), + KNSemiModalOptionKeys.transitionStyle : @(KNSemiModalTransitionStyleSlideUp), + KNSemiModalOptionKeys.disableCancel : @(NO), + }]; } #pragma mark Push-back animation group @@ -94,7 +98,7 @@ -(CAAnimationGroup*)animationGroupForward:(BOOL)_forward { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"]; animation.toValue = [NSValue valueWithCATransform3D:t1]; - CFTimeInterval duration = [[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue]; + CFTimeInterval duration = [[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue]; animation.duration = duration/2; animation.fillMode = kCAFillModeForwards; animation.removedOnCompletion = NO; @@ -116,17 +120,24 @@ -(CAAnimationGroup*)animationGroupForward:(BOOL)_forward { } -(void)kn_interfaceOrientationDidChange:(NSNotification*)notification { - UIView *overlay = [[self parentTarget] viewWithTag:kSemiModalOverlayTag]; - [self kn_addOrUpdateParentScreenshotInView:overlay]; + // if orientation really changed update screen shoot. + // UIDeviceOrientationDidChangeNotification send when display control center or notification panel + UIDeviceOrientation currentOrientationState = [[UIDevice currentDevice] orientation]; + if ((orientationState != currentOrientationState) && (currentOrientationState != UIDeviceOrientationUnknown) + && (currentOrientationState != UIDeviceOrientationFaceUp) && (currentOrientationState != UIDeviceOrientationFaceDown)) { + UIView *overlay = [[self parentTarget] viewWithTag:kSemiModalOverlayTag]; + [self kn_addOrUpdateParentScreenshotInView:overlay]; + orientationState = currentOrientationState; + } } -(UIImageView*)kn_addOrUpdateParentScreenshotInView:(UIView*)screenshotContainer { - UIView *target = [self parentTarget]; - UIView *semiView = [target viewWithTag:kSemiModalModalViewTag]; - - screenshotContainer.hidden = YES; // screenshot without the overlay! - semiView.hidden = YES; - UIGraphicsBeginImageContextWithOptions(target.bounds.size, YES, [[UIScreen mainScreen] scale]); + UIView *target = [self parentTarget]; + UIView *semiView = [target viewWithTag:kSemiModalModalViewTag]; + + screenshotContainer.hidden = YES; // screenshot without the overlay! + semiView.hidden = YES; + UIGraphicsBeginImageContextWithOptions(target.bounds.size, YES, [[UIScreen mainScreen] scale]); if ([target respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { [target drawViewHierarchyInRect:target.bounds afterScreenUpdates:YES]; } else { @@ -134,20 +145,20 @@ -(UIImageView*)kn_addOrUpdateParentScreenshotInView:(UIView*)screenshotContainer } UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - screenshotContainer.hidden = NO; - semiView.hidden = NO; - - UIImageView* screenshot = (id) [screenshotContainer viewWithTag:kSemiModalScreenshotTag]; - if (screenshot) { - screenshot.image = image; - } - else { - screenshot = [[UIImageView alloc] initWithImage:image]; - screenshot.tag = kSemiModalScreenshotTag; - screenshot.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - [screenshotContainer addSubview:screenshot]; - } - return screenshot; + screenshotContainer.hidden = NO; + semiView.hidden = NO; + + UIImageView* screenshot = (id) [screenshotContainer viewWithTag:kSemiModalScreenshotTag]; + if (screenshot) { + screenshot.image = image; + } + else { + screenshot = [[UIImageView alloc] initWithImage:image]; + screenshot.tag = kSemiModalScreenshotTag; + screenshot.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [screenshotContainer addSubview:screenshot]; + } + return screenshot; } @end @@ -155,58 +166,60 @@ -(UIImageView*)kn_addOrUpdateParentScreenshotInView:(UIView*)screenshotContainer @implementation UIViewController (KNSemiModal) -(void)presentSemiViewController:(UIViewController*)vc { - [self presentSemiViewController:vc withOptions:nil completion:nil dismissBlock:nil]; + [self presentSemiViewController:vc withOptions:nil completion:nil dismissBlock:nil]; } -(void)presentSemiViewController:(UIViewController*)vc - withOptions:(NSDictionary*)options { + withOptions:(NSDictionary*)options { [self presentSemiViewController:vc withOptions:options completion:nil dismissBlock:nil]; } -(void)presentSemiViewController:(UIViewController*)vc - withOptions:(NSDictionary*)options - completion:(KNTransitionCompletionBlock)completion - dismissBlock:(KNTransitionCompletionBlock)dismissBlock { + withOptions:(NSDictionary*)options + completion:(KNTransitionCompletionBlock)completion + dismissBlock:(KNTransitionCompletionBlock)dismissBlock { [self kn_registerDefaultsAndOptions:options]; // re-registering is OK - UIViewController *targetParentVC = [self kn_parentTargetViewController]; - - // implement view controller containment for the semi-modal view controller - [targetParentVC addChildViewController:vc]; - if ([vc respondsToSelector:@selector(beginAppearanceTransition:animated:)]) { - [vc beginAppearanceTransition:YES animated:YES]; // iOS 6 - } - objc_setAssociatedObject(self, kSemiModalViewController, vc, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - objc_setAssociatedObject(self, kSemiModalDismissBlock, dismissBlock, OBJC_ASSOCIATION_COPY_NONATOMIC); - [self presentSemiView:vc.view withOptions:options completion:^{ - [vc didMoveToParentViewController:targetParentVC]; - if ([vc respondsToSelector:@selector(endAppearanceTransition)]) { - [vc endAppearanceTransition]; // iOS 6 - } - if (completion) { - completion(); - } - }]; + UIViewController *targetParentVC = [self kn_parentTargetViewController]; + + // implement view controller containment for the semi-modal view controller + [targetParentVC addChildViewController:vc]; + if ([vc respondsToSelector:@selector(beginAppearanceTransition:animated:)]) { + [vc beginAppearanceTransition:YES animated:YES]; // iOS 6 + } + objc_setAssociatedObject(self, kSemiModalViewController, vc, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, kSemiModalDismissBlock, dismissBlock, OBJC_ASSOCIATION_COPY_NONATOMIC); + [self presentSemiView:vc.view withOptions:options completion:^{ + [vc didMoveToParentViewController:targetParentVC]; + if ([vc respondsToSelector:@selector(endAppearanceTransition)]) { + [vc endAppearanceTransition]; // iOS 6 + } + if (completion) { + completion(); + } + }]; } -(void)presentSemiView:(UIView*)view { - [self presentSemiView:view withOptions:nil completion:nil]; + [self presentSemiView:view withOptions:nil completion:nil]; } -(void)presentSemiView:(UIView*)view withOptions:(NSDictionary*)options { - [self presentSemiView:view withOptions:options completion:nil]; + [self presentSemiView:view withOptions:options completion:nil]; } -(void)presentSemiView:(UIView*)view - withOptions:(NSDictionary*)options - completion:(KNTransitionCompletionBlock)completion { - [self kn_registerDefaultsAndOptions:options]; // re-registering is OK - UIView * target = [self parentTarget]; - + withOptions:(NSDictionary*)options + completion:(KNTransitionCompletionBlock)completion { + [self kn_registerDefaultsAndOptions:options]; // re-registering is OK + UIView * target = [self parentTarget]; + if (![target.subviews containsObject:view]) { + orientationState = [[UIDevice currentDevice] orientation]; // Set associative object objc_setAssociatedObject(view, kSemiModalPresentingViewController, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - + // Register for orientation changes, so we can update the presenting controller screenshot [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kn_interfaceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; + // Get transition style NSUInteger transitionStyle = [[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.transitionStyle] unsignedIntegerValue]; @@ -255,10 +268,10 @@ -(void)presentSemiView:(UIView*)view } // Begin overlay animation - if ([[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.pushParentBack] boolValue]) { - [ss.layer addAnimation:[self animationGroupForward:YES] forKey:@"pushedBackAnimation"]; - } - NSTimeInterval duration = [[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue]; + if ([[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.pushParentBack] boolValue]) { + [ss.layer addAnimation:[self animationGroupForward:YES] forKey:@"pushedBackAnimation"]; + } + NSTimeInterval duration = [[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue]; [UIView animateWithDuration:duration animations:^{ ss.alpha = [[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.parentAlpha] floatValue]; }]; @@ -309,7 +322,7 @@ -(void)updateBackground{ [self kn_addOrUpdateParentScreenshotInView:overlay]; } -(void)dismissSemiModalView { - [self dismissSemiModalViewWithCompletion:nil]; + [self dismissSemiModalViewWithCompletion:nil]; } -(void)dismissSemiModalViewWithCompletion:(void (^)(void))completion { @@ -325,22 +338,22 @@ -(void)dismissSemiModalViewWithCompletion:(void (^)(void))completion { [presentingController dismissSemiModalViewWithCompletion:completion]; return; } - + // Correct target for dismissal UIView * target = [self parentTarget]; UIView * modal = [target viewWithTag:kSemiModalModalViewTag]; UIView * overlay = [target viewWithTag:kSemiModalOverlayTag]; - NSUInteger transitionStyle = [[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.transitionStyle] unsignedIntegerValue]; - NSTimeInterval duration = [[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue]; - UIViewController *vc = objc_getAssociatedObject(self, kSemiModalViewController); - KNTransitionCompletionBlock dismissBlock = objc_getAssociatedObject(self, kSemiModalDismissBlock); - - // Child controller containment - [vc willMoveToParentViewController:nil]; - if ([vc respondsToSelector:@selector(beginAppearanceTransition:animated:)]) { - [vc beginAppearanceTransition:NO animated:YES]; // iOS 6 - } - + NSUInteger transitionStyle = [[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.transitionStyle] unsignedIntegerValue]; + NSTimeInterval duration = [[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue]; + UIViewController *vc = objc_getAssociatedObject(self, kSemiModalViewController); + KNTransitionCompletionBlock dismissBlock = objc_getAssociatedObject(self, kSemiModalDismissBlock); + + // Child controller containment + [vc willMoveToParentViewController:nil]; + if ([vc respondsToSelector:@selector(beginAppearanceTransition:animated:)]) { + [vc beginAppearanceTransition:NO animated:YES]; // iOS 6 + } + [UIView animateWithDuration:duration animations:^{ if (transitionStyle == KNSemiModalTransitionStyleSlideUp) { if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){ @@ -374,9 +387,9 @@ -(void)dismissSemiModalViewWithCompletion:(void (^)(void))completion { // Begin overlay animation UIImageView * ss = (UIImageView*)[overlay.subviews objectAtIndex:0]; - if ([[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.pushParentBack] boolValue]) { - [ss.layer addAnimation:[self animationGroupForward:NO] forKey:@"bringForwardAnimation"]; - } + if ([[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.pushParentBack] boolValue]) { + [ss.layer addAnimation:[self animationGroupForward:NO] forKey:@"bringForwardAnimation"]; + } [UIView animateWithDuration:duration animations:^{ ss.alpha = 1; } completion:^(BOOL finished) { @@ -401,8 +414,8 @@ - (void)resizeSemiView:(CGSize)newSize { UIButton * button = (UIButton*)[overlay viewWithTag:kSemiModalDismissButtonTag]; CGRect bf = button.frame; bf.size.height = overlay.frame.size.height - newSize.height; - NSTimeInterval duration = [[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue]; - [UIView animateWithDuration:duration animations:^{ + NSTimeInterval duration = [[self ym_optionOrDefaultForKey:KNSemiModalOptionKeys.animationDuration] doubleValue]; + [UIView animateWithDuration:duration animations:^{ modal.frame = mf; button.frame = bf; } completion:^(BOOL finished) { @@ -430,18 +443,18 @@ @implementation NSObject (YMOptionsAndDefaults) static char const * const kYMStandardDefaultsTableName = "YMStandardDefaultsTableName"; - (void)ym_registerOptions:(NSDictionary *)options - defaults:(NSDictionary *)defaults + defaults:(NSDictionary *)defaults { - objc_setAssociatedObject(self, kYMStandardOptionsTableName, options, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - objc_setAssociatedObject(self, kYMStandardDefaultsTableName, defaults, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, kYMStandardOptionsTableName, options, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, kYMStandardDefaultsTableName, defaults, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (id)ym_optionOrDefaultForKey:(NSString*)optionKey { - NSDictionary *options = objc_getAssociatedObject(self, kYMStandardOptionsTableName); - NSDictionary *defaults = objc_getAssociatedObject(self, kYMStandardDefaultsTableName); - NSAssert(defaults, @"Defaults must have been set when accessing options."); - return options[optionKey] ?: defaults[optionKey]; + NSDictionary *options = objc_getAssociatedObject(self, kYMStandardOptionsTableName); + NSDictionary *defaults = objc_getAssociatedObject(self, kYMStandardDefaultsTableName); + NSAssert(defaults, @"Defaults must have been set when accessing options."); + return options[optionKey] ?: defaults[optionKey]; } @end