apache / cordova-ios

Apache Cordova iOS
https://cordova.apache.org/
Apache License 2.0
2.15k stars 986 forks source link

Keyboard Dismissal Leaves Viewport Shifted in iOS 12 / XCode 10 #417

Closed booleanbetrayal closed 4 years ago

booleanbetrayal commented 5 years ago

Seeing a blocking issue after updating to XCode 10 related to keyboard displacement and then dismissal.

When an input that would require webview centering is clicked, the viewport is repositioned to center that input, as iOS has traditionally done. However, when dismissing the keyboard, the viewport is not re-positioned properly back to its original position.

This can leave large gaps where the webview is no longer visible. In the attached screenshots, you can see that this leaves a large margin where the viewport is rendering, shifted upwards and off-screen by a 100+ pixels. There appears to be no way do re-position the viewport short of additional input focus changes, each resulting in similar offset issues.

To add some additional confusion, it appears this only happens on iOS 12 / XCode 10 produced installs, such that:

Any info on potential workarounds or patches would be greatly appreciated. I've already looked into upgrading our cordova-plugin-ionic-webview plugin (as well as various others), but it appears to not have any relevance with the issue. At this point, we're looking at downgrading XCode in order to get working builds out.


Click Here to See Example Screenshots ---- _Captured via iOS Simulator, but also experienced on various physical devices running iOS 12_ **Prior to focusing an input:** 1 **Input focused: / keyboard present** 2 **Keyboard dismissed / HTML element highlighted in inspector:** ![3](https://user-images.githubusercontent.com/981825/45831984-e6186780-bcbd-11e8-9ba7-283af87ea342.png)

Click Here for Environment Details ---- **OS:** OSX 10.13.6 (High Sierra) **XCode:** 10.0 (10A255) **cordova:** 8.0.0 **cordova-ios:** 4.5.4 **cordova-plugins:** - cordova-android-support-gradle-release 1.2.0 "cordova-android-support-gradle-release" - cordova-custom-config 5.0.2 "cordova-custom-config" - cordova-plugin-app-event 1.2.1 "Application Events" - cordova-plugin-apprate 1.4.0 "AppRate" - cordova-plugin-camera 4.0.3 "Camera" - cordova-plugin-device 2.0.1 "Device" - cordova-plugin-dialogs 2.0.1 "Notification" - cordova-plugin-facebook4 1.9.1 "Facebook Connect" - cordova-plugin-file 6.0.1 "File" - cordova-plugin-googleplus 5.2.1 "Google SignIn" - cordova-plugin-inappbrowser 2.0.2 "InAppBrowser" - cordova-plugin-ionic-webview 1.1.16 "cordova-plugin-ionic-webview" - cordova-plugin-market 1.2.0 "Market" - cordova-plugin-media 4.0.1-dev "Media" - cordova-plugin-nativestorage 2.3.2 "NativeStorage" - cordova-plugin-network-information 2.0.1 "Network Information" - cordova-plugin-spinner-dialog 1.3.1 "SpinnerDialog" - cordova-plugin-splashscreen 5.0.3-dev "Splashscreen" - cordova-plugin-whitelist 1.3.3 "Whitelist" - cordova-plugin-x-socialsharing 5.3.2 "SocialSharing" - cordova-support-google-services 1.1.0 "cordova-support-google-services" - de.appplant.cordova.plugin.local-notification 0.8.5 "LocalNotification" - es6-promise-plugin 4.2.2 "Promise" - phonegap-plugin-multidex 1.0.0 "Multidex" - phonegap-plugin-push 2.2.3 "PushPlugin"

dpogue commented 5 years ago

Does this happen in a plain Cordova app without the Ionic WebView plugin? Does it happen if you use the Apache version of the WKWebView plugin (cordova-plugin-wkwebview-engine)?

booleanbetrayal commented 5 years ago

@dpogue - I AM seeing this when replacing cordova-plugin-ionic-webview with the cordova-plugin-wkwebview-engine WKWebView implementation, but I AM NOT seeing this without WKWebView (UIWebView)

dpogue commented 5 years ago

Are you using viewport-fit=cover in your meta viewport tag?

I'm trying to reproduce this in an iOS 12 simulator with Xcode 10 and plain WKWebView, and I don't see it doing anything different than iOS 11, but that might also be because my page scrolls.

booleanbetrayal commented 5 years ago

We are for iPhoneX / notch support:

<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi, viewport-fit=cover" />

I'm not seeing the issue when removing the viewport-fit=cover but this breaks notch workarounds of course.

dpogue commented 5 years ago

Okay, I've managed to reproduce it. The bad news is that it's not a Cordova bug, because I reproduced it in a demo Swift app with a standard WKWebView.

I'll try to wrap this demo up as a reduced test case and submit it to Apple 😞

Screenshots from my test ![simulator screen shot - iphone xs max - 2018-09-20 at 13 50 44](https://user-images.githubusercontent.com/241708/45846284-43bbac80-bcdc-11e8-8c29-a8a2f1b578da.png) ![simulator screen shot - iphone xs max - 2018-09-20 at 13 50 49](https://user-images.githubusercontent.com/241708/45846294-47e7ca00-bcdc-11e8-8bc3-8c6b3aaa391e.png) ![simulator screen shot - iphone xs max - 2018-09-20 at 13 50 54](https://user-images.githubusercontent.com/241708/45846299-4c13e780-bcdc-11e8-8a03-f5e12cb61a80.png)
booleanbetrayal commented 5 years ago

Was afraid of that. Thanks for confirmation / isolation @dpogue . If you link the issue here, I'll be sure to try and weigh in on its importance, etc.

dpogue commented 5 years ago

Radar filed as rdar://44655885 Mirrored at https://openradar.appspot.com/radar?id=5018321736957952 WebKit bug report at https://webkit.org/b/192564

Please file duplicate radar reports at http://bugreporter.apple.com!

Stefano1964 commented 5 years ago

In one af my app i had to use the ionic wkwebview (i dont use ionic framework) as this version allow to access files outside the app directory and the standard cordova wkwebview doesn't allow this kind of access. I can see this bug ONLY with ionic wkwebview latest (Xcode 10, iOS 12 real device) and only on smartphone, on iPad it's ok (webview back to original position), on the same app switching to default cordova wkwebview the bug is not there. Tested also with xs in simulator, same, with cordova wkwebview it's ok, not with ionic wkwebview. @dpogue you provide a swift project (tested and the bug is present), but wkwebview implementation both in cordova and ionic is an obj-c implemenation, maybe a swift bug?

Bravo2x commented 5 years ago

Hello! In my case i updated cordova-plugin-ionic-webview up to ="^2.1.4" and ionic platform to latest version. For now i have

Ionic:

ionic (Ionic CLI) : 4.1.2 (/usr/local/lib/node_modules/ionic) Ionic Framework : ionic-angular 3.9.2 @ionic/app-scripts : 3.1.11

Cordova:

cordova (Cordova CLI) : 8.0.0 Cordova Platforms : ios 4.5.5 Cordova Plugins : cordova-plugin-ionic-keyboard 2.1.2, cordova-plugin-ionic-webview 2.1.4, (and 12 other plugins)

System:

ios-sim : 5.0.13 NodeJS : v8.11.3 (/usr/local/bin/node) npm : 6.1.0 OS : macOS High Sierra Xcode : Xcode 10.0 Build version 10A255 but it still not enough, you should add to config.xml file:

<preference name="CordovaWebViewEngine" value="CDVWKWebViewEngine" />

if you have issues with CORS after this modification please let me know and i will advice you how to avoid it

Stefano1964 commented 5 years ago

@Bravo2x you have to edit CDVWKWebViewEngine.m: https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/176

booleanbetrayal commented 5 years ago

@Stefano1964 - I have a branch with your suggested changes and am NOT seeing any change regarding keyboard displacement. Are you sure we're talking about the same issue? Are you using viewport-fit=cover"?

Stefano1964 commented 5 years ago

Yes, i'm sure, and yes i'm using viewport-fit=cover", for me this solved the issue.

andrewballester commented 5 years ago

This was killing me. We seemed to find an ugly fix that works without taking viewport-fit:cover off. $('input').on("blur",function (e) { window.scrollTo(0,0); });

AngelaRg commented 5 years ago

For me the solution was changing the keyboard plugin I was using, from ionic-plugin-keyboard (https://github.com/ionic-team/ionic-plugin-keyboard , which was actually deprecated) to cordova-plugin-ionic-keyboard (https://github.com/ionic-team/cordova-plugin-ionic-keyboard). It now works fine.

gueno commented 5 years ago

As @andrewballester suggested it, the ugly fix he found went through my mind (and it hurts..) But when you have a form with multiple fields and using ios keyboard arrows to navigate between them, the screen jumps to the top and if your field was way below the "fold", it is then hidden behind the keyboard

Another ugly fix to fix the first ugly fix is to use another method :

/**
 * iOS 12 bug for keyboard dismissing, view not restored ಠ_ಠ
 */
if (device.ios && device.osVersion.split('.')[0] >= 12) {
    jQuery(document).delegate('input, textarea', 'blur', function(){
        setTimeout(function(){ 
        // did not work for me straight away without a small timeout ಠ_ಠ
        jQuery('html').animate({height: '100.1vh'}, 100, function(){ 
            // did not work for me with just one instruction with 100vh  ಠ_ಠ
            // the easing smooth doesn't work all the time properly ಠ_ಠ
        jQuery(this).animate({height: '100vh'}, 1);
        });
        },100);
    });
}
amirkaromashkin commented 5 years ago

Experience same without Cordova, using pure WKWebView. From native code side the following workaround worked: webView.setNeedsLayout() on keyboard dismissal. UI is re-rendered correctly but instantly, not smoothly like in Safari.

KhanhPhamDinh commented 5 years ago

Hi all, i am facing that issue run on IOS 12 that was built with Xcode 10 (if you build with XCode 9, the issue don't appear). IOS 12 will automatic change content offset of WKScrollview in WKWebview fit with height of keyboard in case keyboardWillShow, but it didn't revert it in case keyboardWillHide. That is my solution to fixed it, hope it help for you.

/**
 * observer notification when keyboard will hide
 */
[[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(keyboardWillHide)
                                                     name:UIKeyboardWillHideNotification
                                                   object:nil];

/////////////--------------------------//////////////
/*
 *Description: this method was trigger by selector keyboarwillhide from notification
 */
-(void)keyboardWillHide
{
    if (@available(iOS 12.0, *)) {
        WKWebView *webview = (WKWebView*)self.webView;
         for(UIView* v in webview.subviews){
              if([v isKindOfClass:NSClassFromString(@"WKScrollView")]){
                      UIScrollView *scrollView = (UIScrollView*)v;
                      [scrollView setContentOffset:CGPointMake(0, 0)];
              }
          }
     }
}
booleanbetrayal commented 5 years ago

Thanks @KhanhPhamDinh ! I've validated that this is working for me in a fork of the cordova-ionic-webview project, and have submitted a PR crediting you: https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/201

dpogue commented 5 years ago

For the record, I strongly suspect the issue was introduced as a side effect in https://github.com/WebKit/webkit/commit/0ff63c48b7456d6b72b0e751a600d9e3d14dbdeb

booleanbetrayal commented 5 years ago

Thanks for doing some digging @dpogue . I've written the author at Apple, so hopefully there is some visibility internally there, now. 🤞

tobeee commented 5 years ago

We found building with Xcode 9 as opposed to 10 reverted any weird keyboard issues (obviously not long term solution)

loomchild commented 5 years ago

Me too, I came to the same conclusion as only downgrading to Xcode 9 helped to remedy the problem.

Does anyone know what is the status of the issue on Apple's side? Is it acknowledged as Xcode bug? Is there a bug report I can subscribe to? I only found this, but it's unofficial and cannot subscribe to notifications: http://www.openradar.me/44655885

Or will it rather be a fix in cordova-plugin-ionic-webview? I tried applying this fix, but it didn't help: https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/201

dpogue commented 5 years ago

That openradar is the closest thing you'll be able to watch. Apple's internal radar system doesn't allow you to see or subscribe to any bugs aside from your own. I submitted that bug and have not heard any response from Apple.

We might be able to hack around it at the plugin level, but the proper fix has to be made by Apple in WKWebView.

thepianist2 commented 5 years ago

My solution was use a plugin or any thing to detect the event on keyboard native hide, and on this moment execute script to normalize the height of the app.

https://github.com/cjpearson/cordova-plugin-keyboard

//install the plugin
cordova plugin add cordova-plugin-keyboard

//on the .js file detect the event and apply the normal height in the event the keyboard will hide.
window.addEventListener('keyboardWillHide', function () {
  document.getElementsByTagName('html')[0].style.height = '101vh';
  setTimeout(()=>{
    document.getElementsByTagName('html')[0].style.height = '100vh';
  }, 40);
});

for the time being

dpolivy commented 5 years ago

I'm seeing a potentially similar issue with UIWebView on iOS 12: if you are scrolled to the bottom of the page with an input field at the bottom and tap in it to open the keyboard, it covers the bottom of the page and does not scroll up like it used to on iOS 11 (and prior). As a result, the input field is completely covered by the keyboard and you can't see what you're typing. I don't know if this is related to the same root WebKit bug, but happy to open another issue if this should be treated separately.

janpio commented 5 years ago

Yes please, if it is not about what is described in the original issue here, open a new one (but if you think it is related, you can of course include a link to this issue in the new one).

mlynch commented 5 years ago

FYI merged the PR for this in cordova-plugin-ionic-webview: https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/201. Out in 2.2.4 shortly

soumenmanna92 commented 5 years ago

Use the following code in device ready. This works for me.

$('input').on("blur",function (e) {
    window.scrollTo(0,0);
});

$('input').on("focus",function (e) {
     var inputpos = $(this).offset().top - 200;        
     window.scrollTo(0, inputpos);
});
jasonfish568 commented 5 years ago

After a few customers reporting such behaviour on Wechat browser, I believe it uses one of the Webkits you guys have probably mentioned. The problem occurred. This only happens on iOS 12. I don't think Wechat is using ionic at all so this should be a bug with the webkit. However, using Chrome doesn't have the same bug.

dpogue commented 5 years ago

This is a bug in WebKit, and has been reported to Apple with a reduced test case project:

Radar filed as rdar://44655885 Mirrored at https://openradar.appspot.com/radar?id=5018321736957952

If you want to raise the priority, file duplicate reports at https://bugreporter.apple.com (no seriously, Apple prioritizes based on how many duplicate reports they get)

jasonfish568 commented 5 years ago

Just filed a duplicate report

ilblog commented 5 years ago

I do confirm that solution/workaround by @thepianist2 presented here https://github.com/apache/cordova-ios/issues/417#issuecomment-438682821 using keyboard plugin works. Xcode 10, iOS12

turkimud commented 5 years ago

This works for me to fix window.scrollTo(0, 0) issue. Add this code in device ready:

var inputYoffset = 0;
jQuery(document).delegate('input:not([type=submit]), textarea', 'focus', function(){
    inputYoffset = window.pageYOffset;
});
jQuery(document).delegate('input:not([type=submit]), textarea', 'blur', function(){
    document.getElementsByTagName('html')[0].style.height = '101vh';
    setTimeout(function() {
        document.getElementsByTagName('html')[0].style.height = '100vh';
        window.scrollTo(0, 0);
        window.scrollTo(0, inputYoffset);
    }, 50);
});
Yuripetusko commented 5 years ago

Is this the same issue?

https://github.com/apache/cordova-plugin-wkwebview-engine/issues/71

dpogue commented 5 years ago

@Yuripetusko yes

martinsotirov commented 5 years ago

Upgrading to cordova-plugin-ionic-webview fixed this issue for me but now I can't scroll on iOS.

Yuripetusko commented 5 years ago

Downgrading to xcode9 and building with it for now helped

dpogue commented 5 years ago

In an attempt to bump up the priority on this, I've also filed https://bugs.webkit.org/show_bug.cgi?id=192564

ghost commented 5 years ago

That is my solution to fixed it, hope it help for you.

The fix from @KhanhPhamDinh works as expected, thanks a lot!

vuthanhict commented 5 years ago

 if #available(iOS 11.0, *) {
             self.webView.scrollView.contentInsetAdjustmentBehavior = .never
 } else {
            self.automaticallyAdjustsScrollViewInsets = false
 }

And UIScrollViewDelegate

 func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) {
       Logger.print("scrollViewDidChangeAdjustedContentInset")
         if #available(iOS 12.0, *) {
           for view in webView.subviews {
                if let scrollView = view as? UIScrollView {
                   scrollView.setContentOffset(.zero, animated: true)
                 }
             }
        }
}

Worked

rohorhweb commented 5 years ago

How to fix this issue if i am forced to use only phonegap build? Setting html height is not fix for me, because i am using absolute/fixed positioned divs which are ignoring my HTML height...

cvanem commented 5 years ago

I filed a duplicate report with Apple. Hopefully we can get this resolved sooner than later.

Does anyone happen to have a workaround for this issue when using cordova-plugin-inappbrowser? I could not get any of the above solutions to work and can't downgrade to xCode 9 as all my devices are running iOS 12.

FWiner commented 5 years ago

Waiting for the solution


 if #available(iOS 11.0, *) {
             self.webView.scrollView.contentInsetAdjustmentBehavior = .never
 } else {
            self.automaticallyAdjustsScrollViewInsets = false
 }

And UIScrollViewDelegate

 func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) {
       Logger.print("scrollViewDidChangeAdjustedContentInset")
         if #available(iOS 12.0, *) {
           for view in webView.subviews {
                if let scrollView = view as? UIScrollView {
                   scrollView.setContentOffset(.zero, animated: true)
                 }
             }
        }
}

Worked Which file should i add the code?

rohorhweb commented 5 years ago

still facing issue... any update on this?

dpogue commented 5 years ago

Nope. There has been no response from Apple to any of the bug reports so far.

There is no correct way for Cordova (or Ionic) to solve this in our code. We can try to hack around it, but the real problem must be fixed by Apple.

cvanem commented 5 years ago

For anyone using cordova inappbrowser, here is a slightly modified fix of mlynch's post above that works nicely for inappbrowser:

Make the following changes to file: CDVWKInAppBrowser.m

1. Add this directly before the pluginInitialize function:

NSTimer *timer;

2. Add this to the bottom of the pluginInitialize function:

 [[NSNotificationCenter defaultCenter]
 addObserver:self
 selector:@selector(keyboardWillHide)
 name:UIKeyboardWillHideNotification object:nil];

[[NSNotificationCenter defaultCenter]
 addObserver:self
 selector:@selector(keyboardWillShow)
 name:UIKeyboardWillShowNotification object:nil];   

3. Add these directly below the pluginInitialize function:

 -(void)keyboardWillHide
{       
    if (@available(iOS 12.0, *)) {
        timer = [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(keyboardDisplacementFix) userInfo:nil repeats:false];
        [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
}

 -(void)keyboardWillShow
{    
    if (timer != nil) {
        [timer invalidate];
    }
}

 -(void)keyboardDisplacementFix
{    
    if ([self.inAppBrowserViewController.webView respondsToSelector:@selector(scrollView)]) { 
        [UIView animateWithDuration:.25 animations:^{
            ((UIScrollView*)[self.inAppBrowserViewController.webView scrollView]).contentOffset = CGPointMake(0, 0);
        }];        
    } 
 }
mlynch commented 5 years ago

Having trouble following this issue. For anyone that used our webview (cordova-plugin-ionic-webview), is the issue resolved? We're happy to add other hacks to work around this in the meantime just not sure where things are at...

rewhex commented 5 years ago

Based on some JS comments above, this one is working for me:

document.addEventListener('focusout', function (event) {
      const targetNode = event.target

      if (targetNode) {
        const tagName = targetNode.tagName.toLowerCase()

        if (targetNode.type != 'submit' && (tagName == 'input' || tagName == 'textarea')) {
          htmlNode.style.height = '100.1vh'

          setTimeout(function () {
            htmlNode.style.height = '100vh'
          }, 50)
        }
      }
    })
cvanem commented 5 years ago

@mlynch I believe everyone has either found a work-around or is waiting for an actual fix from Apple. I believe your fix resolved the issue for those using cordova-plugin-ionic-webview. I made some slight modifications to your fix to use it in the latest cordova-plugin-inappbrowser code as I could not find another working solution for this scenario. Your fix also appears to be working well there.

cvanem commented 5 years ago

FYI, I got an email today, from Apple, indicating that they closed my bug report because it was a duplicate of 44655885.