diff --git a/.gitignore b/.gitignore index bb1e388..b1ab962 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ user.*.espressostorage Sparkle/*.app *.zip PXListView +*.xccheckout diff --git a/ExampleApp/KGAppDelegate.m b/ExampleApp/KGAppDelegate.m index ed96142..7e60b75 100644 --- a/ExampleApp/KGAppDelegate.m +++ b/ExampleApp/KGAppDelegate.m @@ -28,6 +28,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [showButton addTarget:self action:@selector(showAction:) forControlEvents:UIControlEventTouchUpInside]; [self.window.rootViewController.view addSubview:showButton]; + [KGModal sharedInstance].responsiveToKeyboard = YES; [KGModal sharedInstance].closeButtonType = KGModalCloseButtonTypeRight; [self.window makeKeyAndVisible]; @@ -57,30 +58,27 @@ - (void)showAction:(id)sender{ welcomeLabel.shadowOffset = CGSizeMake(0, 1); [contentView addSubview:welcomeLabel]; - CGRect infoLabelRect = CGRectInset(contentView.bounds, 5, 5); - infoLabelRect.origin.y = CGRectGetMaxY(welcomeLabelRect)+5; - infoLabelRect.size.height -= CGRectGetMinY(infoLabelRect) + 50; - UILabel *infoLabel = [[UILabel alloc] initWithFrame:infoLabelRect]; - infoLabel.text = @"KGModal is an easy drop in control that allows you to display any view " + CGRect infoTextRect = CGRectInset(contentView.bounds, 5, 5); + infoTextRect.origin.y = CGRectGetMaxY(welcomeLabelRect)+5; + infoTextRect.size.height -= CGRectGetMinY(infoTextRect) + 50; + UITextView *infoText = [[UITextView alloc] initWithFrame:infoTextRect]; + infoText.text = @"KGModal is an easy drop in control that allows you to display any view " "in a modal popup. The modal will automatically scale to fit the content view " - "and center it on screen with nice animations!"; - infoLabel.numberOfLines = 6; - infoLabel.textColor = [UIColor whiteColor]; - infoLabel.textAlignment = NSTextAlignmentCenter; - infoLabel.backgroundColor = [UIColor clearColor]; - infoLabel.shadowColor = [UIColor blackColor]; - infoLabel.shadowOffset = CGSizeMake(0, 1); - [contentView addSubview:infoLabel]; + "and center it on screen with nice animations!\n" + "(Tap here and then tap button below)"; + infoText.textColor = [UIColor blackColor]; + infoText.textAlignment = NSTextAlignmentCenter; + infoText.backgroundColor = [UIColor whiteColor]; + [contentView addSubview:infoText]; - CGFloat btnY = CGRectGetMaxY(infoLabelRect)+5; + CGFloat btnY = CGRectGetMaxY(infoTextRect)+5; CGFloat btnH = CGRectGetMaxY(contentView.frame)-5 - btnY; UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect]; - btn.frame = CGRectMake(infoLabelRect.origin.x, btnY, infoLabelRect.size.width, btnH); + btn.frame = CGRectMake(infoTextRect.origin.x, btnY, infoTextRect.size.width, btnH); [btn setTitle:@"Close Button Right" forState:UIControlStateNormal]; [btn addTarget:self action:@selector(changeCloseButtonType:) forControlEvents:UIControlEventTouchUpInside]; [contentView addSubview:btn]; -// [[KGModal sharedInstance] setCloseButtonLocation:KGModalCloseButtonLocationRight]; [[KGModal sharedInstance] showWithContentView:contentView andAnimated:YES]; } @@ -105,7 +103,9 @@ - (void)changeCloseButtonType:(id)sender{ KGModal *modal = [KGModal sharedInstance]; KGModalCloseButtonType type = modal.closeButtonType; - if(type == KGModalCloseButtonTypeLeft){ + [modal endEditing:NO]; + + if (type == KGModalCloseButtonTypeLeft) { modal.closeButtonType = KGModalCloseButtonTypeRight; [button setTitle:@"Close Button Right" forState:UIControlStateNormal]; }else if(type == KGModalCloseButtonTypeRight){ diff --git a/ExampleApp/KGModalExample.xcodeproj/project.xcworkspace/xcshareddata/KGModalExample.xccheckout b/ExampleApp/KGModalExample.xcodeproj/project.xcworkspace/xcshareddata/KGModalExample.xccheckout deleted file mode 100644 index b2f46a1..0000000 --- a/ExampleApp/KGModalExample.xcodeproj/project.xcworkspace/xcshareddata/KGModalExample.xccheckout +++ /dev/null @@ -1,41 +0,0 @@ - - - - - IDESourceControlProjectFavoriteDictionaryKey - - IDESourceControlProjectIdentifier - 5C978CE1-221A-4169-AF81-B64C6BBF112C - IDESourceControlProjectName - KGModalExample - IDESourceControlProjectOriginsDictionary - - FB6C026C-C1C4-455C-BA11-CFF8C0912A70 - https://github.com/kgn/KGModal.git - - IDESourceControlProjectPath - ExampleApp/KGModalExample.xcodeproj/project.xcworkspace - IDESourceControlProjectRelativeInstallPathDictionary - - FB6C026C-C1C4-455C-BA11-CFF8C0912A70 - ../../.. - - IDESourceControlProjectURL - https://github.com/kgn/KGModal.git - IDESourceControlProjectVersion - 110 - IDESourceControlProjectWCCIdentifier - FB6C026C-C1C4-455C-BA11-CFF8C0912A70 - IDESourceControlProjectWCConfigurations - - - IDESourceControlRepositoryExtensionIdentifierKey - public.vcs.git - IDESourceControlWCCIdentifierKey - FB6C026C-C1C4-455C-BA11-CFF8C0912A70 - IDESourceControlWCCName - KGModal - - - - diff --git a/KGModal.h b/KGModal.h index 10ce934..b47b9c9 100644 --- a/KGModal.h +++ b/KGModal.h @@ -26,6 +26,8 @@ typedef NS_ENUM(NSUInteger, KGModalCloseButtonType){ @interface KGModal : NSObject +@property (nonatomic) BOOL responsiveToKeyboard; + // Determines if the modal should dismiss if the user taps outside of the modal view // Defaults to YES @property (nonatomic) BOOL tapOutsideToDismiss; @@ -79,4 +81,7 @@ typedef NS_ENUM(NSUInteger, KGModalCloseButtonType){ // run the completion after the modal is hidden - (void)hideAnimated:(BOOL)animated withCompletionBlock:(void(^)())completion; +// Send endEditing message to containing view +-(void) endEditing:(BOOL) force; + @end diff --git a/KGModal.m b/KGModal.m index 1ac5a57..4633ddd 100644 --- a/KGModal.m +++ b/KGModal.m @@ -9,10 +9,20 @@ #import "KGModal.h" #import +#pragma mark - Utils for Keyboard response + +#define swap(a, b) do{typeof(a) odd13var=a; a=b; b=odd13var;}while(0) + +@interface UIView (KGFirstResponder) +- (UIView *)kgFindFirstResponder; +@end + +#pragma mark - + CGFloat const kFadeInAnimationDuration = 0.3; CGFloat const kTransformPart1AnimationDuration = 0.2; CGFloat const kTransformPart2AnimationDuration = 0.1; -CGFloat const kDefaultCloseButtonPadding = 17.0; +CGFloat const kDefaultPadding = 17.0; NSString *const KGModalGradientViewTapped = @"KGModalGradientViewTapped"; @@ -80,7 +90,7 @@ -(void)setCloseButtonType:(KGModalCloseButtonType)closeButtonType { CGRect closeFrame = self.closeButton.frame; if(closeButtonType == KGModalCloseButtonTypeRight){ - closeFrame.origin.x = round(CGRectGetWidth(self.containerView.frame)-kDefaultCloseButtonPadding-CGRectGetWidth(closeFrame)/2); + closeFrame.origin.x = round(CGRectGetWidth(self.containerView.frame)-kDefaultPadding-CGRectGetWidth(closeFrame)/2); }else{ closeFrame.origin.x = 0; } @@ -88,6 +98,85 @@ -(void)setCloseButtonType:(KGModalCloseButtonType)closeButtonType { } } +#pragma mark - Methods for Keyboard response + +-(void)setResponsiveToKeyboard:(BOOL)responsiveToKeyboard { + _responsiveToKeyboard = responsiveToKeyboard; + + // Clear anything before + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; + + // Add if needed + if(responsiveToKeyboard){ + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWasShown:) + name:UIKeyboardDidShowNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillBeHidden:) + name:UIKeyboardWillHideNotification + object:nil]; + } +} + +-(void) _adjustPositionForKeyboard:(CGSize) keyboardSize inTime:(double) duration { + CGRect containerViewRect = self.containerView.frame; + CGRect freeScreen = self.window.bounds; + + if (UIDeviceOrientationIsLandscape(self.window.rootViewController.interfaceOrientation)) { + swap(freeScreen.size.width, freeScreen.size.height); + } + + // TODO: could be more accurate + // Simple go through. Should work on 99% cases, but may have exceptions anyways. + freeScreen.size.height -= keyboardSize.height; + CGFloat newY = (freeScreen.size.height - containerViewRect.size.height)/2 + freeScreen.origin.y; + + // A threshhold to check if animation really needed + if (ABS(newY - containerViewRect.origin.y) < 2) { + containerViewRect.origin.y = newY; + self.containerView.frame = containerViewRect; + } else { + containerViewRect.origin.y = newY; + UIView *container = self.containerView; + [UIView animateWithDuration:duration + animations:^{ + container.frame = containerViewRect; + }]; + } +} + +-(void) keyboardWasShown:(NSNotification *) aNotification { + UIView *selectedView = [self.containerView kgFindFirstResponder]; + // Do only this modal contains the firstResponder + if (selectedView) { + CGSize keyboardSize = [[[aNotification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; + if (UIDeviceOrientationIsLandscape(self.window.rootViewController.interfaceOrientation)) + swap(keyboardSize.width, keyboardSize.height); + + if ([selectedView respondsToSelector:@selector(inputAccessoryView)]) { + UIView *accessoryView = [selectedView inputAccessoryView]; + if (accessoryView) { + keyboardSize = CGSizeMake(keyboardSize.width, + keyboardSize.height + accessoryView.frame.size.height); + } + } + + double animDuration = [[[aNotification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + + [self _adjustPositionForKeyboard:keyboardSize inTime:animDuration]; + } +} + +-(void) keyboardWillBeHidden:(NSNotification *) aNotification { + double animDuration = [[[aNotification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + + [self _adjustPositionForKeyboard:CGSizeZero inTime:animDuration]; +} + +#pragma mark - + - (void)showWithContentView:(UIView *)contentView{ [self showWithContentView:contentView andAnimated:YES]; } @@ -110,8 +199,7 @@ - (void)showWithContentView:(UIView *)contentView andAnimated:(BOOL)animated { self.window.rootViewController = viewController; self.viewController = viewController; - CGFloat padding = 17; - CGRect containerViewRect = CGRectInset(contentView.bounds, -padding, -padding); + CGRect containerViewRect = CGRectInset(contentView.bounds, -kDefaultPadding, -kDefaultPadding); 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)); @@ -120,19 +208,12 @@ - (void)showWithContentView:(UIView *)contentView andAnimated:(BOOL)animated { containerView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin| UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin; containerView.layer.rasterizationScale = [[UIScreen mainScreen] scale]; - contentView.frame = (CGRect){padding, padding, contentView.bounds.size}; + contentView.frame = (CGRect){kDefaultPadding, kDefaultPadding, 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; @@ -143,6 +224,9 @@ - (void)showWithContentView:(UIView *)contentView andAnimated:(BOOL)animated { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tapCloseAction:) name:KGModalGradientViewTapped object:nil]; + // Force adding keyboard observers if needed + [self setResponsiveToKeyboard:self.responsiveToKeyboard]; + // The window has to be un-hidden on the main thread // This will cause the window to display dispatch_async(dispatch_get_main_queue(), ^{ @@ -243,6 +327,10 @@ - (void)setModalBackgroundColor:(UIColor *)modalBackgroundColor{ } } +-(void)endEditing:(BOOL)force { + [self.containerView endEditing:force]; +} + - (void)dealloc{ [self cleanup]; } @@ -427,3 +515,24 @@ - (UIImage *)closeButtonImage{ } @end + +#pragma mark - + +@implementation UIView (KGFirstResponder) + +- (UIView *)kgFindFirstResponder +{ + if (self.isFirstResponder) + return self; + + for (UIView *subView in self.subviews) { + UIView *firstResponder = [subView kgFindFirstResponder]; + + if (firstResponder != nil) { + return firstResponder; + } + } + return nil; +} +@end +