diff --git a/README.md b/README.md index 838b69b..f1fe717 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This plugin was based on this Apache [project](https://github.com/apache/cordova - [Keyboard.disableScrollingInShrinkView](#keyboarddisablescrollinginshrinkview) - [Keyboard.hide](#keyboardhide) - [Keyboard.show](#keyboardshow) + - [Keyboard.setKeyboardAnimator](#keyboardsetkeyboardanimator) - [Properties](#properties) - [Keyboard.isVisible](#keyboardisvisible) - [Keyboard.automaticScrollToTopOnHiding](#keyboardautomaticscrolltotoponhiding) @@ -172,6 +173,36 @@ This is allows to fix issue with elements declared with position: fixed, after keyboard is hiding. +#### Supported Platforms + +- iOS + +## Keyboard.setKeyboardAnimator + +Assign a function that will handle content animation in the browser + + Keyboard.setKeyboardAnimator(function(fromHeight, toHeight, animationDurationInMs, animationCompleteCallback) + { + // Example with jQuery (http://jquery.com/) and Velocity.JS (http://julian.com/research/velocity/) + $('body').velocity({height: [to, from]}, {duration: animationDurationInMS, easing: "easeOutQuad", complete: function() + { + // Tells the plugin that client side has finished animation + animationCompleteCallback(); + }}); + }); + +#### Description + +Assign a function that will handle animation when the keyboard appears and disappears. +Remember to execute the provided callback to let the plugin know when animation has finished. +The parameters given is which height the webview will animate from and to, the duration for which the keyboard will animate and a callback method to call when the animation has been completed. + +Worth noting is that window.resize will be triggered after animation has been completed when keyboard is showing, but before animation is started when hiding. +This is due to the fact that the webview will resize after animation has been completed when the keyboard is appearing so that the animation is not clipped, and +when the keyboard is hiding it will resize to the full height so that the animation out can be completed without clipping as well. + +Thanks to [@glyuck](https://github.com/glyuck) for this approach from his project [GLKAnimateWebViewFrame](https://github.com/glyuck/GLKAnimateWebViewFrame) which is the base for this feature + #### Supported Platforms - iOS diff --git a/src/ios/CDVKeyboard.h b/src/ios/CDVKeyboard.h index 17707d8..5771cec 100644 --- a/src/ios/CDVKeyboard.h +++ b/src/ios/CDVKeyboard.h @@ -35,5 +35,8 @@ - (void)disableScrollingInShrinkView:(CDVInvokedUrlCommand*)command; - (void)hideFormAccessoryBar:(CDVInvokedUrlCommand*)command; - (void)hide:(CDVInvokedUrlCommand*)command; +- (void)animationComplete:(CDVInvokedUrlCommand *)command; +- (void)enableAnimation:(CDVInvokedUrlCommand *)command; +- (void)disableAnimation:(CDVInvokedUrlCommand *)command; @end diff --git a/src/ios/CDVKeyboard.m b/src/ios/CDVKeyboard.m index 3c04548..377e580 100644 --- a/src/ios/CDVKeyboard.m +++ b/src/ios/CDVKeyboard.m @@ -31,7 +31,20 @@ @interface CDVKeyboard () @end -@implementation CDVKeyboard +@interface AnimationDetails : NSObject +@property (nonatomic, readwrite, assign) CGFloat from; +@property (nonatomic, readwrite, assign) CGFloat to; +@property (nonatomic, readwrite, assign) CGRect screen; +@end + +@implementation AnimationDetails + +@end; + +@implementation CDVKeyboard { + AnimationDetails *_animationDetails; + BOOL _shouldAnimateWebView; +} - (id)settingForKey:(NSString*)key { @@ -44,6 +57,7 @@ - (void)pluginInitialize { NSString* setting = nil; + _shouldAnimateWebView = NO; setting = @"HideKeyboardFormAccessoryBar"; if ([self settingForKey:setting]) { self.hideFormAccessoryBar = [(NSNumber*)[self settingForKey:setting] boolValue]; @@ -159,6 +173,7 @@ - (void)setShrinkView:(BOOL)shrinkView - (void)shrinkViewKeyboardWillChangeFrame:(NSNotification*)notif { + BOOL shouldAnimate = _shouldAnimateWebView; // No-op on iOS 7.0. It already resizes webview by default, and this plugin is causing layout issues // with fixed position elements. We possibly should attempt to implement shrinkview = false on iOS7.0. // iOS 7.1+ behave the same way as iOS 6 @@ -189,6 +204,8 @@ - (void)shrinkViewKeyboardWillChangeFrame:(NSNotification*)notif screen = full; } + CGFloat currentScreenHeight = self.webView.frame.size.height; + // Get the intersection of the keyboard and screen and move the webview above it // Note: we check for _shrinkView at this point instead of the beginning of the method to handle // the case where the user disabled shrinkView while the keyboard is showing. @@ -199,8 +216,51 @@ - (void)shrinkViewKeyboardWillChangeFrame:(NSNotification*)notif self.webView.scrollView.scrollEnabled = !self.disableScrollingInShrinkView; } + CGFloat newScreenHeight = screen.size.height; + // When keyboard will be hidden, willShow and show is triggered again + // even though keyboard is already visible, ignoring as early as possible + if(newScreenHeight == self.webView.frame.size.height) + { + return; + } + // A view's frame is in its superview's coordinate system so we need to convert again - self.webView.frame = [self.webView.superview convertRect:screen fromView:self.webView]; + if(!shouldAnimate) + { + self.webView.frame = [self.webView.superview convertRect:screen fromView:self.webView]; + return; + } + + NSDictionary* userInfo = [notif userInfo]; + NSNumber *durationValue = userInfo[UIKeyboardAnimationDurationUserInfoKey]; + NSTimeInterval duration = durationValue.doubleValue; + + BOOL isGrowing = newScreenHeight > currentScreenHeight; + + // If webView is growing, change it's frame imediately, so it's content is not clipped during animation + if (isGrowing) { + self.webView.frame = [self.webView.superview convertRect:screen fromView:self.webView]; + } + // Tell JS that it can start animating with values + NSString *javascriptString = [NSString stringWithFormat:@"Keyboard.beginAnimation(%f, %f, %f)", currentScreenHeight, newScreenHeight, duration*1000]; + [self.commandDelegate evalJs: javascriptString]; + + _animationDetails = [[AnimationDetails alloc] init]; + _animationDetails.from = currentScreenHeight; + _animationDetails.to = newScreenHeight; + _animationDetails.screen = screen; + + // alternative to using animationComplete but the timer can finish before + // the browser is finished animating, thereby clipping the animation + +// __weak typeof(self) weakSelf = self; +// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ +// __strong typeof(weakSelf) self = weakSelf; +// // If webview was shrinking, change it's frame after animation is complete +// if (!isGrowing) { +// self.webView.frame = [self.webView.superview convertRect:screen fromView:self.webView]; +// } +// }); } #pragma mark UIScrollViewDelegate @@ -228,7 +288,7 @@ - (void)shrinkView:(CDVInvokedUrlCommand*)command self.shrinkView = [value boolValue]; } - + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.shrinkView] callbackId:command.callbackId]; } @@ -243,7 +303,7 @@ - (void)disableScrollingInShrinkView:(CDVInvokedUrlCommand*)command self.disableScrollingInShrinkView = [value boolValue]; } - + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.disableScrollingInShrinkView] callbackId:command.callbackId]; } @@ -255,10 +315,10 @@ - (void)hideFormAccessoryBar:(CDVInvokedUrlCommand*)command if (!([value isKindOfClass:[NSNumber class]])) { value = [NSNumber numberWithBool:NO]; } - + self.hideFormAccessoryBar = [value boolValue]; } - + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.hideFormAccessoryBar] callbackId:command.callbackId]; } @@ -268,6 +328,34 @@ - (void)hide:(CDVInvokedUrlCommand*)command [self.webView endEditing:YES]; } +// JS indicates that it finished handling Keyboard animation +- (void)animationComplete:(CDVInvokedUrlCommand*)command +{ + if(!_animationDetails) + { + return; + } + + BOOL isGrowing = [_animationDetails from] < [_animationDetails to]; + // If webview was shrinking, change it's frame after animation is complete + if (!isGrowing) { + self.webView.frame = [self.webView.superview convertRect:[_animationDetails screen] fromView:self.webView]; + } + _animationDetails = nil; +} + +// JS indicates that it wants to handle Keyboard animation +- (void)enableAnimation:(CDVInvokedUrlCommand *)command +{ + _shouldAnimateWebView = YES; +} + +// JS indicates that it finished handling Keyboard animation +- (void)disableAnimation:(CDVInvokedUrlCommand*)command +{ + _shouldAnimateWebView = NO; +} + #pragma mark dealloc - (void)dealloc diff --git a/www/keyboard.js b/www/keyboard.js index e93ef69..df5d0e6 100644 --- a/www/keyboard.js +++ b/www/keyboard.js @@ -22,7 +22,7 @@ var argscheck = require('cordova/argscheck'), utils = require('cordova/utils'), exec = require('cordova/exec'); - + var Keyboard = function() { }; @@ -55,7 +55,7 @@ Keyboard.fireOnShow = function() { cordova.fireWindowEvent('keyboardDidShow'); if(Keyboard.onshow) { - Keyboard.onshow(); + Keyboard.onshow(); } }; @@ -64,7 +64,16 @@ Keyboard.fireOnHide = function() { cordova.fireWindowEvent('keyboardDidHide'); if(Keyboard.onhide) { - Keyboard.onhide(); + Keyboard.onhide(); + } +}; + +Keyboard.setKeyboardAnimator = function(animator) { + Keyboard.onKeyboardAnimate = animator; + if(typeof Keyboard.onKeyboardAnimate === "function") { + exec(null, null, "Keyboard", "enableAnimation", []); + } else { + exec(null, null, "Keyboard", "disableAnimation", []); } }; @@ -80,7 +89,7 @@ Keyboard.fireOnHiding = function() { cordova.fireWindowEvent('keyboardWillHide'); if(Keyboard.onhiding) { - Keyboard.onhiding(); + Keyboard.onhiding(); } }; @@ -88,7 +97,7 @@ Keyboard.fireOnShowing = function() { cordova.fireWindowEvent('keyboardWillShow'); if(Keyboard.onshowing) { - Keyboard.onshowing(); + Keyboard.onshowing(); } }; @@ -100,6 +109,17 @@ Keyboard.hide = function() { exec(null, null, "Keyboard", "hide", []); }; +var animationComplete = function() { + exec(null, null, "Keyboard", "animationComplete", []); +}; + +Keyboard.beginAnimation = function(from, to, duration) { + if(typeof Keyboard.onKeyboardAnimate === 'function') { + Keyboard.onKeyboardAnimate(from, to, duration, animationComplete); + } +}; + +Keyboard.onKeyboardAnimate = null; Keyboard.isVisible = false; Keyboard.automaticScrollToTopOnHiding = false;