Ionic-framework: iOS; scrolling broken in lists with inputs

Created on 11 Mar 2016  路  17Comments  路  Source: ionic-team/ionic-framework

Short description of the problem:

On iOS scrolling sometimes stops working in lists. It only happens when the list consists of inputs elements like input. When input focus is obtained scrolling sometimes ceases to function. It seems to be a result of the keyboard. It is not possible to regain the ability to scroll; only by leaving the view and entering it again.

Image
After the last two inputs I am not able to scroll. The scroll ceases to function.

What behavior are you expecting?

Scrolling should work regardless of what elements are used. Input included <ion-input>. Also with cordova.plugins.Keyboard.disableScroll(true) set scroll should reengage when keyboard disappears.

Steps to reproduce:

  1. set cordova.plugins.Keyboard.disableScroll(true)
  2. Make a view with a <div class="list>
  3. Fill the div with inputs <ion-input>
  4. Scroll down to the bottom elements
  5. Select element for input
  6. Close the keyboard
  7. Scrolling is now 'locked' and not possible (this is not completely reproducible, but it happens very frequently)

Ionic Version: 1.2.x

Browser & Operating System: iOS 9.3 or lower

Run ionic info from terminal/cmd prompt:

Cordova CLI: 5.1.1
Gulp version:  CLI version 3.9.0
Gulp local:   Local version 3.9.0
Ionic Version: 1.2.4-nightly-1917
Ionic CLI Version: 1.7.14
Ionic App Lib Version: 0.7.0
ios-deploy version: 1.7.0
ios-sim version: 4.1.1
OS: Mac OS X El Capitan
Node Version: v0.12.2
Xcode version: Xcode 7.2.1 Build version 7C1002

Most helpful comment

Yes your code works.

I've discovered the root to the problem. Native scrolling. I thought I had only disabled javascript scrolling for Android but in fact I disabled it on all platforms.

So the root of this problem was; $ionicConfigProvider.scrolling.jsScrolling(false). This breaks scrolling on iOS in some cases. I'm sorry but it is something I only just realized.

I guess you can close this issue now. Again sorry for the trouble.

All 17 comments

This is a pretty significant problem!

Anyone have a fix until this is properly taken care of?

This (or something very similar) affects me also, using Safari on an iPad Air, iOS 9.2.

In my case, selecting checkbox items frequently freezes scrolling.

Behaviour is actually quite odd:

<body>
<ion-pane>
<div style="position:absolute; top:0; left:0; right:0;height:20px">HEADER</div>
<ion-content style="padding-top: 20px">
<div class="card">
<ion-checkbox [etc]></ion-checkbox>
....
</div>
</ion-content>
</ion-pane>
</body>

Normally, the header div is stuck to the top, and the cards/list-items scroll happily underneath it.
After tapping a checkbox item, about one in three times, the cards/list-items stay static while the _absolutely-positioned header_ scrolls up and down!

We are using a custom directive on our ion-content elements, combined with commenting out some style settings in ionic.js (overflow-scroll hidden/scroll). Our directive looks like this:

        .module('quiply')
        .directive('qyToggleOverflowScroll', qyToggleOverflowScroll);

    /* @ngInject */
    function qyToggleOverflowScroll($timeout, $window, qyIonicReady) {
        var directive = {
            restrict: 'A',
            link: link
        };
        return directive;

        function link(scope, element, attrs) {
            var domElement = element[0];

            qyIonicReady().then(function onQyIonicReady() {
                $window.addEventListener('native.keyboardshow', handleKeyboardShow);
                $window.addEventListener('native.keyboardhide', handleKeyboardHide);

                // remove event listener on destroy
                scope.$on('$destroy', removeKeyboardHandlerListener);

                function handleKeyboardShow() {
                    console.log('qyOverflowScrollToggle keyboard show: ELEMENT CLASS LIST: '
                        + domElement.classList.toString());
                    // iOS or Android full screen
                    var isIosOrAndroidFullScreen
                        = Config.OS.isIOS || (Config.OS.isAndroid && ionic.Platform.isFullScreen);

                    if (isIosOrAndroidFullScreen) {
                        // wrap in timeout to avoid screen flicker
                        //$timeout(function () {
                        console.log('qyOverflowScrollToggle: '
                            + 'keyboard is shown, set overflow-y to: scroll');
                        domElement.style.overflowY = 'hidden';
                        //element.css('overflow-y', 'hidden');
                        //element.css('-webkit-overflow-scrolling', 'auto');
                        //});

                        $timeout(function setOverflowYToScrollIfNeeded() {
                            var scrollerHeight = element.height();
                            var scrollerContentHeight = domElement.scrollHeight;

                            // if scroller contains enough content to enable scrolling
                            if (scrollerContentHeight > scrollerHeight + 1) {
                                console.log('qyOverflowScrollToggle keyboard show: '
                                            + 'scroller height / scroller content height: '
                                            + scrollerHeight
                                            + ' / '
                                            + scrollerContentHeight);

                                console.log('qyOverflowScrollToggle keyboard show: '
                                            + 'content larger than scroller, set overflow-y to: scroll');
                                domElement.style.overflowY = 'scroll';
                                //element.css('overflow-y', 'scroll');
                                //element.css('-webkit-overflow-scrolling', 'touch');
                            }
                        }, 400);
                    }
                }

                function handleKeyboardHide() {
                    console.log('qyOverflowScrollToggle keyboard hide: ELEMENT CLASS LIST: '
                        + domElement.classList.toString());
                    //// iOS or Android full screen
                    var isIosOrAndroidFullScreen
                        = Config.OS.isIOS || (Config.OS.isAndroid && ionic.Platform.isFullScreen);

                    if (isIosOrAndroidFullScreen) {
                        // wrap in timeout to avoid screen flicker
                        //$timeout(function () {
                        element.css('overflow-y', 'hidden');
                        element.css('-webkit-overflow-scrolling', 'auto');
                        //});

                        $timeout(function setOverflowYToScrollIfNeeded() {
                            //var scrollerHeight = element.height();
                            var scrollerHeight = domElement.clientHeight;
                            var scrollerContentHeight = domElement.scrollHeight;

                            console.log('qyOverflowScrollToggle keyboard hide: '
                                + 'scroller height / scroller content height: '
                                + scrollerHeight
                                + ' / '
                                + scrollerContentHeight);

                            // if scroller contains enough content to enable scrolling
                            if (scrollerContentHeight > scrollerHeight + 1) {
                                console.log('qyOverflowScrollToggle keyboard hide: '
                                    + 'content larger than scroller, set overflow-y to: scroll');
                                element.css('-webkit-overflow-scrolling', 'touch');
                                element.css('overflow-y', 'scroll');
                            }
                        }, 400);
                    }
                }

                function removeKeyboardHandlerListener() {
                    $window.removeEventListener('native.keyboardshow', handleKeyboardShow);
                    $window.removeEventListener('native.keyboardshow', handleKeyboardHide);
                }
            });
        }
    }

We use it on ion-content elements like this:

<ion-content class="has-header" qy-toggle-overflow-scroll>

What it does is that whenever the keyboard opens or closes, overflow-scroll is set to hidden to avoid disappearing content / blank views. After a keyboard transition has been completed, we set overflow-scroll back to scroll if the content height requires scrolling.

At the same time, we have commented out following code in release/ionic.js of version 1.2.4:

line 7204/7205
//self.el.style.overflowY = 'hidden';
//self.el.style.overflowX = 'hidden';

These 2 commented out lines of code are the ones breaking scrolling - the issue you are experiencing - as under some circumstances overflow-scroll does not get set back to scroll. Our custom directive fixes this for us.

We have commented out a couple of other lines of code in ionic.js but as far as I remember these do not have anything to do with this very issue.

Thank you for the feedback, and explanation, it is greatly appreciated!

I am not able to get your directive to work... I'm getting some: TypeError: qyIonicReady is not a function
Am I missing something?

Well, qyIonicReady is a custom service we are using. It's basically wrapping $ionicPlatform.ready() so you could just use $ionicPlatform.ready() instead.

Thank you it makes sense! It is still not working for me though;
Error: element.height is not a function. (In 'element.height()', 'element.height' is undefined)

Problem is in: function setOverflowYToScrollIfNeeded

These 2 lines element is undefined;

var scrollerHeight = element.height();
var scrollerContentHeight = domElement.scrollHeight;

You are probably not using jQuery but we are. Therefore, the height() method is not defined for you as jqLite is being used and not the full jQuery. Instead of calling height() you'd have to use something equivalent, e.g. element.offsetHeight. Some more info here: https://github.com/oneuijs/You-Dont-Need-jQuery#2.2

@leschirmeur You're a winner! Thank you so much, got it working!

I really hope we get an official fix soon!

@Tobiaswk, can you share a sample project or codepen I can use to recreate this? We have created a sample here to try to fix but I cannot recreate it. I am running iOS 9.3.1 on an iPhone, and I can't recreate on simulator either.

Executing ionic start input-scrolling-bug http://embed.plnkr.co/xhwyuIN1f7mnHPDq9UMx should pull down the project for you in a fashion that you can run it on a device, etc.

When you say "6. Scrolling is now 'locked' and not possible (this is not completely reproducible, but it happens very frequently)", how often are we talking? I tried executing the steps about 20 times and I can't get it to happen.

Thanks,
Dan

@danbucholtz I don't see you use cordova.plugins.Keyboard.disableScroll(true) in your example project. This is the root of the problem. Using cordova.plugins.Keyboard.disableScroll(true) of course means you can't scroll with the keyboard open/up. The problem is that scrolling never reengages when the keyboard at some point is closed.

Hi @Tobiaswk, I will try again. We must have added that function call to a different but similar project. I will keep you posted.

Thanks,
Dan

Hi @Tobiaswk,

I still cannot recreate this on my iPhone 6S.

Can you try pulling down the plunkr using the ionic start the-plunkr-test http://embed.plnkr.co/xhwyuIN1f7mnHPDq9UMx and then add the cordova logic as follows to the app.js file?

angular.module('starter', ['ionic', 'starter.controllers'])

.run(function($ionicPlatform) {
  $ionicPlatform.ready(function() {
    StatusBar.styleDefault();
    if(window.cordova && window.cordova.plugins.Keyboard) {
      //alert("Cordova initialized");
      //cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
      cordova.plugins.Keyboard.disableScroll(true);
    }
  });
})

Please let me know how that goes. Hopefully we can figure it out and solve it.

Thanks,
Dan

Thank you for reaching out! We're closer!

With the plunkr you provided I'm not able to reproduce the problem. The thing is you're using;

<ion-item>
    <input type="text" placeholder="Input">
</ion-item>
...

Scroll works with the above with elements spanding the screen height and more.

What I'm doing is the following;

<label class="item item-input">
    <span class="input-label">Username</span>
    <input type="text">
</label>
...

Scroll is not working at all when using the above with elements spanding the screen height and more.

@Tobiaswk, I'll test today and get back to you soon. Thanks!

@Tobiaswk, I still can't recreate. Can you try with this code and let me know? I am using the exact steps as you are in the gif. Just run ionic serve on the project and it should work.

I did update to match the mark-up you provided in the post above.

Thanks,
Dan

Yes your code works.

I've discovered the root to the problem. Native scrolling. I thought I had only disabled javascript scrolling for Android but in fact I disabled it on all platforms.

So the root of this problem was; $ionicConfigProvider.scrolling.jsScrolling(false). This breaks scrolling on iOS in some cases. I'm sorry but it is something I only just realized.

I guess you can close this issue now. Again sorry for the trouble.

@Tobiaswk Great solution to the problem.

Was this page helpful?
0 / 5 - 0 ratings