watusi / jquery-mobile-iscrollview

JQuery Mobile widget plug-in for easy use of the iScroll javascript scroller.
407 stars 183 forks source link

IOS keyboard popup issue with header #123

Open hadi77makkawi opened 11 years ago

hadi77makkawi commented 11 years ago

I am creating a web app using JQM + Iscroll everything is working perfectly but I have an issue which is if the page have input text the the user clicks on it the keyboard will popup and the header goes up its not fixed anymore it seemed like ios keyboard popup pushed the while uiwebview to center the page to the clicked input text.

Sorry for my bad English and I wish if someone can advise me with any soluations

jtara commented 11 years ago

I'm guessing you are experiencing this on iOS?

The problem is the browser tries to center the focused element by scrolling the page. But scrolling the page isn't effective if you are using iScroll on the page.

Unfortunately, there is not a good solution that I know of. I have solved this, but only for native apps that use a uiwebview. I've written an extension for Rhodes. One could certainly be written for PhoneGap using the same techniques. The extension is needed in order to know when the keyboard appears and disappears (focus/blur unfortunately is not enough to do the job) as well as the size of the keyboard.

I believe, though, that iOS 7 may provide a path for better dealing with the virtual keyboard, because I think that for iOS7 now the browser actually resizes the viewport when the virtual keyboard appears.

Closing this because it is a duplicate, and there is currently no solution.

hadi77makkawi commented 11 years ago

Thanks for this explanation

twilly86 commented 11 years ago

Hey @HadiMakawi,

Have you had any luck with this? I am able to get semi-decent results when I set the following configs.

window.Keyboard.shrinkView(true); window.Keyboard.hideFormAccessoryBar(false); window.Keyboard.disableScrollingInShrinkView(true);

disableScrollingInShrinkView is fine with IScroll since iscroll handles all the scrolling for us and allows jquery mobile footers to stay fixed at the bottom.

I'm wresting with the phonegap 3.2 keyboard plugin now. I can't get the webview resize to look alright. The webview always gets pushed up and then down during the resize.

Any ideas?

jtara commented 11 years ago

twilly86: can you clarify where the 3 functions you referenced above come from? Is this new ios7 functionality? Android functionality? (new/recent?) PhoneGap functionality (new/recent?) Some PhoneGap plugin(s). I'm not coming up with any useful Google search results.

I haven't done any PhoneGap work since 2.2, so I'm pretty clueless on anything more recent. I have a client whose done his own update to PhoneGap 3.2, though, and so 'll have something I can experiment with. (The app doesn't have any inputs, but I can add some for an experiment.)

Since I do have this working quite well in Rhodes with a custom plugin, I'll perhaps have an idea of what needs to be addressed to work with these functions.

I'm re-opening this for now, because it sounds like there is at least a glimmer of hope to do this without a custom plugin.

hadi77makkawi commented 11 years ago

Hey guys I have managed to solve the issue I will provide you with the solution after the weekend (:

A brief of what I did .. I get rid of Iscroll plugin I did some code changes on Cordova ViewControler class. Also I have added some Objective C code to stop pushing the uiwebview when keyboard opens and to call a Java script function when keyboard is open. This java script function re adjust the height of the HTML page after keyboard is up.

jtara commented 11 years ago

Hadi - Yes, this is exactly the approach that I took to solve the problem for Rhodes - a combination of Objective-C code to stop pushing the UIWebView when the keyboard opens, and send a JS event when the keyboard opens (sending the size of the keyboard). Then a JS functon adjusts the height of the page. When the keyboard is closed, the above is reversed.

ios7 throws a monkey-wrench, because now it shows the page under the keyboard. One should then be able to use iScroll and no scrolling of the UIWebView will occur, as long as the page height does not exceed the viewport height - and this is in fact normally the case if you are using iScroll. But this is optional, so if you tell it not to show the view under the keyboard, then it should work like ios6. In fact, if you build for iOS6, it just works the way it did previously.

I think it's a matter of taste. I do prefer the old way, as I don't like the content showing under the keyboard. I think current apps are 50/50 on this choice.

The last piece (that allows you to use iScroll, is to use a focus event to run some JS that will scroll the input into view when each field is focused. I've impleted a simple scheme where it just centers the input. A better way would be to do it intelligently, only scrolling if the input is not in view, and then only scrolling enough to bring the input into view. Then it would work similar to the native behavior. Of course, you could implement any kind of behaviour you would like on focus.

If you want to implement this, I can probably help you out with some odd logic around the focus events.

The more intelligent scrolling is something I am tasked to do, but not a very high priority. I do hope that I'll be permitted to release this code (a plugin for Rhodes, and the JS code for iscrollview) as open source at some point. Good to see there is a solution on the horizon for PhoneGap.

hadi77makkawi commented 11 years ago

@jtara @twilly86

I will explain how I have solved this issue. Am using Cordova 3.1.0, JQM 1.3.2 and fastclick.js (ftlabs-jsv2)

Objective-C: In CDVViewContoller.m under Cordova package I have commented the following:

1- In (void)viewDidLoad function // [[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification // object:nil // queue:[NSOperationQueue mainQueue] // usingBlock:^(NSNotification* notification) { // [weakSelf performSelector:@selector(keyboardWillShow:) withObject:notification afterDelay:0]; // }];

2- Comment the whole - (void)keyboardWillShow:(NSNotification*)notif function:

//- (void)keyboardWillShow:(NSNotification)notif //{ // if (![@"true" isEqualToString :[self settingForKey:@"KeyboardShrinksView"]]) { //
// CGRect keyboardFrame = [notif.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; // keyboardFrame = [self.view convertRect:keyboardFrame fromView:nil]; //
// CGRect newFrame = self.view.bounds; // CGFloat accessoryHeight = gAccessoryBarHeight; // CGFloat actualKeyboardHeight = (keyboardFrame.size.height - accessoryHeight); // newFrame.size.height -= actualKeyboardHeight; //
// NSString * jsCallBack = [NSString stringWithFormat:@"keyboardShow('%.f')",newFrame.size.height]; // [self.webView stringByEvaluatingJavaScriptFromString:jsCallBack]; //
// return; // } // // CGRect keyboardFrame = [notif.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; // keyboardFrame = [self.view convertRect:keyboardFrame fromView:nil]; // // CGRect newFrame = self.view.bounds; // CGFloat accessoryHeight = gAccessoryBarHeight; // CGFloat actualKeyboardHeight = (keyboardFrame.size.height - accessoryHeight); // newFrame.size.height -= actualKeyboardHeight; // //
//
//// NSLog(@" cordova show : %.2f", newFrame.size.height); //
// self.webView.frame = newFrame; // self.webView.scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0); //
// NSString \
jsCallBack = [NSString stringWithFormat:@"keyboardShow('%.f')", newFrame.size.height]; // [self.webView stringByEvaluatingJavaScriptFromString:jsCallBack]; // // BOOL disableScrollingWhenKeyboardShrinksView = [@"true" isEqualToString :[self settingForKey:@"DisableScrollingWhenKeyboardShrinksView"]]; // if (disableScrollingWhenKeyboardShrinksView) { // self.webView.scrollView.scrollEnabled = NO; // } //
//

//} 3- (void)keyboardWillHide:(NSNotification*)notif to be like that

}

4- In MainViewController.m

4.1 in function - (id)init add the following at the end: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];

4.2 add the following function in MainViewController.m -(void)keyboardWillShow:(NSNotification *)aNotification {

NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
NSString * jsCallBack = [NSString stringWithFormat:@"keyboardShow('%.f')", kbSize.height];
[self.webView stringByEvaluatingJavaScriptFromString:jsCallBack];

float x = self.webView.scrollView.bounds.origin.x;
float y = self.webView.scrollView.bounds.origin.y;
CGPoint originalOffset = CGPointMake(x, y);

for (double p = 0.0; p < 0.1; p += 0.001) {
    [self setContentOffset:originalOffset withDelay:p];
}

self.webView.scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);

} 4.3 add the following function in MainViewController.m -(void)setContentOffset:(CGPoint)originalOffset withDelay:(double)delay { dispatch_after(0, dispatch_get_main_queue(), ^(void){ [self.webView.scrollView setContentOffset:originalOffset animated:NO];

});

}

5 - My config.xml have the following "fullscreen" value="true" "webviewbounce" value="false "DisallowOverscroll" value="true" "DisableScrollingWhenKeyboardShrinksView" value="false" "KeyboardShrinksView" value="true"

6 - In css I have the following for header, page and content: .ui-header { position: fixed !important; top: 0px !important; right:0px; left:0px; text-align: center; z-index: 1000; width: 100%; height: 40px !important; background: #aa2719 !important; border: none !important; padding: 0px !important; font-weight: normal; opacity: 1 !important;

  }

.ui-page {

    backface-visibility:hidden !important; 
    -webkit-backface-visibility:hidden !important; 
    -moz-backface-visibility:hidden !important;
    -webkit-transform: rotateY(0deg) !important;
    -moz-transform: rotateY(0deg) !important;
    transform: rotateY(0deg) !important;
    -webkit-transform: translateX(0px) !important; 
    -webkit-transform: translateZ(0px) !important; 
    -webkit-transform: translate3d(0, 0, 0) !important;
    -webkit-box-orient: vertical;
    -webkit-box-pack: justify;
    overflow: hidden !important;
    border: none !important;
  }

.ui-content { border: none !important; margin: 0px !important; position: absolute !important; bottom:0px !important; left: 0px !important; right:0px !important; top:40px !important; padding:25px 15px 0px 15px !important; z-index: 500 !important; overflow-y: auto;

     -webkit-overflow-scrolling: touch !important;
    -webkit-transform: rotateY(0deg) !important;
    -moz-transform: rotateY(0deg) !important;
    transform: rotateY(0deg) !important;
    -webkit-transform: translateX(0px) !important; 
    -webkit-transform: translateZ(0px) !important; 
    -webkit-transform: translate3d(0, 0, 0) !important;
    backface-visibility:hidden !important; 
    -webkit-backface-visibility:hidden !important; 
    -moz-backface-visibility:hidden !important;

     background: #f1f1f1 !important;
  }

7- JS functions: var needsFocus_flag=false; var viewportHeight = document.documentElement.clientHeight; var global_focus_element=null;

$(document).on('touchstart', 'input, textarea, select', function(e) {

 global_focus_element=$("#"+$(this).attr('id'));
 needsFocus_flag=needsFocus(global_focus_element.prop("tagName").toLowerCase());

});

$(document).on('touchend', 'input, textarea, select', function(e) {

 global_focus_element=$("#"+$(this).attr('id'));
 needsFocus_flag=needsFocus(global_focus_element.prop("tagName").toLowerCase());

});

$(document).on('focus', 'input, textarea, select', function(e) {

global_focus_element=$("#"+$(this).attr('id'));
needsFocus_flag=needsFocus(global_focus_element.prop("tagName").toLowerCase());

});

function needsFocus (type) {

switch (type) {

case 'input':
case 'button':
case 'select':
case 'textarea':
case 'checkbox':
case 'file':
case 'image':
case 'radio':
case 'submit':
  return true;

default: return false; } };

function keyboardShow(keyboardheight){

if (!$('body').hasClass('keyboard_show')) {
      $('body').addClass('keyboard_show');
      $.mobile.silentScroll(0);
      document.body.scrollTop = 0;          
      active_scroll($.mobile.activePage.find('.ui-content'),viewportHeight-keyboardheight);

      setTimeout(function() {
               scroll_to(keyboardheight);
    }, 35);    
}

} function keyboardHide(){ if ($('.ui-focus').length==0) { $('body').removeClass('keyboard_show');

       $.mobile.resetActivePageHeight();
       active_scroll($.mobile.activePage.find('.ui-content'),viewportHeight);
 }

}

function active_scroll(el,height){

 $.mobile.resetActivePageHeight(viewportHeight+1);
el.css({ 'overflow-y': 'hidden'});
 setTimeout(function() {
        $.mobile.resetActivePageHeight(height);
         el.css({'overflow-y': 'auto'});
 }, 0);

}

function scroll_to(keyboardheight){

   // In this function am checking if element need to be focused also if there is a need to scroll to which means some time even when keyboard is up the inupt u clicked is already in top (:
 if (needsFocus_flag && (global_focus_element.offset().top+global_focus_element.height()) > (viewportHeight-keyboardheight)) {

     var ui_contentOffset =  $.mobile.activePage.find('.ui-content').offset().top;
     var ui_contentScrolTop =  $.mobile.activePage.find('.ui-content').scrollTop();
     var elementOffset = global_focus_element.offset().top;                 
     var final_value=(elementOffset-ui_contentOffset-65)+ui_contentScrolTop;
      $.mobile.activePage.find('.ui-content').stop().animate({ scrollTop: final_value},600, 'easeOutSine');
  };    

}

8 - in jquery.mobile-1.3.2.js 8.1 search for getScreenHeight and replace its code by the following: if (this.last_width == null || this.last_height == null || this.last_width != $( window ).width()) { this.last_height = window.innerHeight || $( window ).height(); this.last_width = $(window).width(); } return this.last_height; 8.2 comment the following line in jquery.mobile-1.3.2.js // $.mobile.window.bind( "throttledresize", $.mobile.resetActivePageHeight );

Finally this is it, and sorry for this a lot of code and my bad English ): I hop you guys can get any benefit from it .

Note: that app am doing is Portrait no Landscape I have not test it in Landscape.

epochDEV commented 9 years ago

That is by far, the worst workaround of any issue or bug that I have ever encountered...

jtara commented 9 years ago

This plugin addresses the native part of this issue, and could probably replace the native code above:

https://github.com/driftyco/ionic-plugins-keyboard

As well, I now have permission to release my own native code (for RhoMobile Rhodes platform) as open-source, and will coordinate with the Ionic project above.