Skip to content

Commit

Permalink
Refactor how hiding the accessory bar works. Relates to #3
Browse files Browse the repository at this point in the history
  • Loading branch information
cjpearson committed Jul 24, 2015
1 parent 6d57af6 commit 9fc1f2f
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 128 deletions.
8 changes: 2 additions & 6 deletions src/ios/CDVKeyboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,13 @@
@protected
id _keyboardShowObserver, _keyboardHideObserver, _keyboardWillShowObserver, _keyboardWillHideObserver;
@protected
id _hideFormAccessoryBarKeyboardShowObserver, _hideFormAccessoryBarKeyboardHideObserver;
@protected
id _shrinkViewKeyboardWillChangeFrameObserver;
@protected
CGFloat _accessoryBarHeight;
}

@property (readwrite, assign) BOOL shrinkView;
@property (readwrite, assign) BOOL disableScrollingInShrinkView;
@property (readwrite, assign) BOOL hideFormAccessoryBar;
@property (readonly, assign) BOOL keyboardIsVisible;
@property (readwrite, assign) BOOL hideFormAccessoryBar;
@property (readonly, assign) BOOL keyboardIsVisible;

- (void) shrinkView:(CDVInvokedUrlCommand*)command;
- (void) disableScrollingInShrinkView:(CDVInvokedUrlCommand*)command;
Expand Down
137 changes: 15 additions & 122 deletions src/ios/CDVKeyboard.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Licensed to the Apache Software Foundation (ASF) under one

#import "CDVKeyboard.h"
#import <Cordova/CDVAvailability.h>
#import <objc/runtime.h>

#ifndef __CORDOVA_3_2_0
#warning "The keyboard plugin is only supported in Cordova 3.2 or greater, it may not work properly in an older version. If you do use this plugin in an older version, make sure the HideKeyboardFormAccessoryBar and KeyboardShrinksView preference values are false."
Expand Down Expand Up @@ -102,8 +103,6 @@ - (void)pluginInitialize
}];

self.webView.scrollView.delegate = self;

_accessoryBarHeight = 44;
}

#pragma mark HideFormAccessoryBar
Expand All @@ -113,131 +112,31 @@ - (BOOL)hideFormAccessoryBar
return _hideFormAccessoryBar;
}

static IMP originalImp;

- (void)setHideFormAccessoryBar:(BOOL)ahideFormAccessoryBar
{
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
__weak CDVKeyboard* weakSelf = self;

if (ahideFormAccessoryBar == _hideFormAccessoryBar) {
return;
}

if (ahideFormAccessoryBar) {
[nc removeObserver:_hideFormAccessoryBarKeyboardShowObserver];
_hideFormAccessoryBarKeyboardShowObserver = [nc addObserverForName:UIKeyboardWillShowNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification* notification) {
// we can't hide it here because the accessory bar hasn't been created yet, so we delay on the queue
[weakSelf performSelector:@selector(formAccessoryBarKeyboardWillShow:) withObject:notification afterDelay:0];
}];

[nc removeObserver:_hideFormAccessoryBarKeyboardHideObserver];
_hideFormAccessoryBarKeyboardHideObserver = [nc addObserverForName:UIKeyboardWillHideNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification* notification) {
[weakSelf formAccessoryBarKeyboardWillHide:notification];
}];
} else {
[nc removeObserver:_hideFormAccessoryBarKeyboardShowObserver];
[nc removeObserver:_hideFormAccessoryBarKeyboardHideObserver];

// if a keyboard is already visible (and the accessorybar was hidden), hide observer will NOT be called, so we observe it once
if (self.keyboardIsVisible && _hideFormAccessoryBar) {
_hideFormAccessoryBarKeyboardHideObserver = [nc addObserverForName:UIKeyboardWillHideNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification* notification) {
[weakSelf formAccessoryBarKeyboardWillHide:notification];
[[NSNotificationCenter defaultCenter] removeObserver:_hideFormAccessoryBarKeyboardHideObserver];
}];
}
}

_hideFormAccessoryBar = ahideFormAccessoryBar;
}

- (NSArray*)getKeyboardViews:(UIView*)viewToSearch{
NSArray *subViews;

for (UIView *possibleFormView in viewToSearch.subviews) {
if ([[possibleFormView description] hasPrefix: self.getKeyboardFirstLevelIdentifier]) {
if(IsAtLeastiOSVersion(@"8.0")){
for (UIView* subView in possibleFormView.subviews) {
return subView.subviews;
}
}else{
return possibleFormView.subviews;
}
}

}
return subViews;
}

- (NSString*)getKeyboardFirstLevelIdentifier{
if(!IsAtLeastiOSVersion(@"8.0")){
return @"<UIPeripheralHostView";
}else{
return @"<UIInputSetContainerView";
}
}

- (void)formAccessoryBarKeyboardWillShow:(NSNotification*)notif
{
if (!_hideFormAccessoryBar) {
return;
}

UIWindow *keyboardWindow = nil;
for (UIWindow *windows in [[UIApplication sharedApplication] windows]) {
if (![[windows class] isEqual:[UIWindow class]]) {
keyboardWindow = windows;
break;
}
}
Class webBrowserClass = NSClassFromString(@"UIWebBrowserView");
Method method = class_getInstanceMethod(webBrowserClass, @selector(inputAccessoryView));

for (UIView* peripheralView in [self getKeyboardViews:keyboardWindow]) {
if (ahideFormAccessoryBar){
originalImp = method_getImplementation(method);

// hides the backdrop (iOS 7)
if ([[peripheralView description] hasPrefix:@"<UIKBInputBackdropView"]) {
// check that this backdrop is for the accessory bar (at the top),
// sparing the backdrop behind the main keyboard
CGRect rect = peripheralView.frame;
if (rect.origin.y == 0) {
[[peripheralView layer] setOpacity:0.0];
}
}
IMP newImp = imp_implementationWithBlock(^(id _s){
return nil;
});

// hides the accessory bar
if ([[peripheralView description] hasPrefix:@"<UIWebFormAccessory"]) {
//remove the extra scroll space for the form accessory bar
CGRect newFrame = self.webView.scrollView.frame;
newFrame.size.height += peripheralView.frame.size.height;
self.webView.scrollView.frame = newFrame;

_accessoryBarHeight = peripheralView.frame.size.height;

// remove the form accessory bar
if(IsAtLeastiOSVersion(@"8.0")){
[[peripheralView layer] setOpacity:0.0];
}else{
[peripheralView removeFromSuperview];
}

}
// hides the thin grey line used to adorn the bar (iOS 6)
if ([[peripheralView description] hasPrefix:@"<UIImageView"]) {
[[peripheralView layer] setOpacity:0.0];
}
method_setImplementation(method, newImp);
}
else {
method_setImplementation(method, originalImp);
}
}

- (void)formAccessoryBarKeyboardWillHide:(NSNotification*)notif
{
// restore the scrollview frame
self.webView.scrollView.frame = CGRectMake(0, 0, self.webView.frame.size.width, self.webView.frame.size.height);
_hideFormAccessoryBar = ahideFormAccessoryBar;
}

#pragma mark KeyboardShrinksView
Expand Down Expand Up @@ -280,13 +179,7 @@ - (void)shrinkViewKeyboardWillChangeFrame:(NSNotification*)notif
// The webview should always be able to return to full size
CGRect keyboardIntersection = CGRectIntersection(screen, keyboard);
if(CGRectContainsRect(screen, keyboardIntersection) && !CGRectIsEmpty(keyboardIntersection) && _shrinkView && self.keyboardIsVisible){

screen.size.height -= keyboardIntersection.size.height;

if (_hideFormAccessoryBar){
screen.size.height += _accessoryBarHeight;
}

self.webView.scrollView.scrollEnabled = !self.disableScrollingInShrinkView;
}

Expand Down

7 comments on commit 9fc1f2f

@rumit91
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change hideFormAccessoryBar(true) no longer works with the WKWebView Cordova plugin, because WKWebView does not use the UIWebBrowserView class.

Looks like with the previous code the plugin would attach observers to the keyboard events and then look for the affected webview instance and apply the change there. Now the change is applied to the webview class which does not account for pluggable webviews (or the WKWebView). I tried changing Class webBrowserClass = NSClassFromString(@"UIWebBrowserView"); to Class webBrowserClass = self.webView.class; to get the class of the instance, but that didn't work.

Do you have any suggestions for correcting this problem?

@cjpearson
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the dumped iOS 9 headers, it seems WKContentView also has the inputAccessoryView property. Swizzling the method on that class instead of (or in addition to) UIWebBrowserView may work.

@cjpearson
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could try this code that swizzles both the UIWebView and WKWebView implementations. It seems to work for me in a quick test.

static IMP UIOriginalImp;
static IMP WKOriginalImp;

- (void)setHideFormAccessoryBar:(BOOL)ahideFormAccessoryBar
{
    if (ahideFormAccessoryBar == _hideFormAccessoryBar) {
        return;
    }

    Method UIMethod = class_getInstanceMethod(NSClassFromString(@"UIWebBrowserView"), @selector(inputAccessoryView));
    Method WKMethod = class_getInstanceMethod(NSClassFromString(@"WKContentView"), @selector(inputAccessoryView));

    if (ahideFormAccessoryBar) {
        UIOriginalImp = method_getImplementation(UIMethod);
        WKOriginalImp = method_getImplementation(WKMethod);

        IMP newImp = imp_implementationWithBlock(^(id _s) {
            return nil;
        });

        method_setImplementation(UIMethod, newImp);
        method_setImplementation(WKMethod, newImp);
    } else {
        method_setImplementation(UIMethod, UIOriginalImp);
        method_setImplementation(WKMethod, WKOriginalImp);
    }

    _hideFormAccessoryBar = ahideFormAccessoryBar;
}

@rumit91
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @cjpearson! I'll give it a try later today.

@andreialecu
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any luck with this?

@cjpearson
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andreialecu, I've checked this change into master. If you install the plugin from github it should work

@andreialecu
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works nicely now. Thanks!

Please sign in to comment.