Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support animating webview when keyboard appears #22

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/ios/CDVKeyboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
100 changes: 94 additions & 6 deletions src/ios/CDVKeyboard.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,20 @@ @interface CDVKeyboard () <UIScrollViewDelegate>

@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
{
Expand All @@ -44,6 +57,7 @@ - (void)pluginInitialize
{
NSString* setting = nil;

_shouldAnimateWebView = NO;
setting = @"HideKeyboardFormAccessoryBar";
if ([self settingForKey:setting]) {
self.hideFormAccessoryBar = [(NSNumber*)[self settingForKey:setting] boolValue];
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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];
}
Expand All @@ -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];
}
Expand All @@ -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];
}
Expand All @@ -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
Expand Down
30 changes: 25 additions & 5 deletions www/keyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
var argscheck = require('cordova/argscheck'),
utils = require('cordova/utils'),
exec = require('cordova/exec');

var Keyboard = function() {
};

Expand Down Expand Up @@ -55,7 +55,7 @@ Keyboard.fireOnShow = function() {
cordova.fireWindowEvent('keyboardDidShow');

if(Keyboard.onshow) {
Keyboard.onshow();
Keyboard.onshow();
}
};

Expand All @@ -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", []);
}
};

Expand All @@ -80,15 +89,15 @@ Keyboard.fireOnHiding = function() {
cordova.fireWindowEvent('keyboardWillHide');

if(Keyboard.onhiding) {
Keyboard.onhiding();
Keyboard.onhiding();
}
};

Keyboard.fireOnShowing = function() {
cordova.fireWindowEvent('keyboardWillShow');

if(Keyboard.onshowing) {
Keyboard.onshowing();
Keyboard.onshowing();
}
};

Expand All @@ -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;

Expand Down