In Ionic 2, how to float an element above the keyboard when the keyboard shows?

22,801

Solution 1

I found a solution which works for me on IOS.

When you inspect the <ion-item> with <ion-input> in the browser(debugging use Safari for IOS) you can find that ionic generates a <div class='input-cover'> which has the style position: absolute;.

Write a CSS which overrides this as below

.input-cover {
  position: static;
}

This did the trick for me and now when you focus on an input field, it scrolls into view and does not hide below the keyboard anymore and all this works buttery smooth.

Solution 2

I've also needed to implement this. I did it and it works perfectly.

1st you need to use cordova plugins keyboard, and at the start call cordova.plugins.Keyboard.disableScroll(true); so the keyboard will not push your view up. 2nd you need to listen on keyboardshow and keyboard hide events like this with a the handlers:

cordova.plugins.Keyboard.disableScroll(true);
                    window.addEventListener('native.keyboardshow', this.dispatchMe);
                    window.addEventListener('native.keyboardhide', this.dispatchMeHide);

    dispatchMe(e) {
        var event = new CustomEvent('keyboardShown');
        event['keyboardHeight'] = e.keyboardHeight;
        document.dispatchEvent(event);
    }

    dispatchMeHide() {
        var event = new CustomEvent('keyboardShown');
        event['closed'] = true;
        document.dispatchEvent(event);
    }

Than you can make an observable from event like this:

this.keyboardObservable = Observable.fromEvent(document, 'keyboardShown');

Than you can listen to that observable. If the keyboard is open than you change the height of the container where your messages are shown. You basically have to make it lower for the height of the keyboard. Here is how I did that

this.chatService.keyboardObservable
            .subscribe(data => {
                if (data.closed) {
                    this.sectionHeight = 85 + '%';
                    this.inputBottom = 0 + '%';
                }
                else {
                    this.docHeight = document.body.clientHeight;
                    this.sectionHeight = ((this.docHeight - data.keyboardHeight - (document.getElementById('toptoolbar').clientHeight + document.getElementById('inputchat').clientHeight)) / this.docHeight) * 100 + '%';
                    this.inputBottom = data.keyboardHeight / this.docHeight * 100 + '%';
                }

            });

and you change these properties with ngStyle like this

[ngStyle]="{'height': sectionHeight}"

I also needed this for chatapp, and now it works perfectly (even if you rotate the screen portrait/landscape mode), the input always floats above the keyboard just like in the native apps :)

I hope this will help you!

Solution 3

The solution I ended up using and one that I'm satisfied with is:

  1. Removing Keyboard.disableScroll(true);
  2. Using a regular <input type="text"> instead of <ion-input type="text">

Works perfectly now!

Solution 4

I was having this problem with Android, so I created a service method that I could put into individual components. It's based on using <ion-input> fields inside of an <ion-content> tag.

This takes advantage of the setScrollTop method that was added to the Content class.

Service

export class KeyboardService {

    autoKeyboardScroll(content:Content, scrollBackAfterKeyboardClose?:boolean) {
        if (!content) {
            return;
        }
        var previousScrollTop = null;
        function onKeyboardShow(e) {
            // if the content no longer exists, stop the listener
            if (removeListenersForMissingContent()) {
                return;
            }
            previousScrollTop = content.getScrollTop();
            // find the input that's currently in focus
            var focusedElement = document.activeElement;
            if (focusedElement && ['INPUT', 'TEXTAREA'].indexOf(focusedElement.tagName)!==-1) {
                // determine the total offset between the top of the "ion-content" and this element.
                // we will do this by climbing up the dom until we reach the "ion-content"
                var offsetTop = focusedElement.offsetTop + focusedElement.scrollHeight;
                var parentEl = focusedElement.offsetParent;
                while (parentEl && parentEl.tagName!=='ION-CONTENT') {
                    offsetTop += parentEl.offsetTop;
                    parentEl = parentEl.offsetParent;
                }
                // we want to move the input so that the bottom of the focused input is just above the keyboard
                var contentDimensions = content.getContentDimensions();
                var newScrollTop = offsetTop - (contentDimensions.contentHeight - focusedElement.scrollHeight);
                content.setScrollTop(newScrollTop);
            }
        }
        function onKeyboardHide(e) {
            // if the content no longer exists, stop the listener
            if (removeListenersForMissingContent()) {
                return;
            }
            // set the scroll top back to the initial position, if requested
            if (scrollBackAfterKeyboardClose) {
                content.setScrollTop(previousScrollTop);
            }
        }
        function removeListenersForMissingContent() {
            // if there is no content, remove the keyboard listeners
            if (!content || content.getContentDimensions().contentHeight===0) {
                window.removeEventListener('native.keyboardshow', onKeyboardShow);
                window.removeEventListener('native.keyboardhide', onKeyboardHide);
                return true;
            }
        }
        // setup listeners
        window.addEventListener('native.keyboardshow', onKeyboardShow);
        window.addEventListener('native.keyboardhide', onKeyboardHide);
    }
}

Component

@Component({
    template: `<ion-content>
        <ion-list>
            <ion-item>
                <div style="height: 400px"></div>
            </ion-item>
            <ion-item>
                <ion-label>Field 1</ion-label>
                <ion-input type="text"></ion-input>
            </ion-item>
            <ion-item>
                <ion-label>Field 2</ion-label>
                <ion-input type="text"></ion-input>
            </ion-item>
            <ion-item>
                <ion-label>Field 3</ion-label>
                <ion-input type="text"></ion-input>
            </ion-item>
            <ion-item>
                <ion-label>Field 4</ion-label>
                <ion-input type="text"></ion-input>
            </ion-item>
            <ion-item>
                <ion-label>Field 5</ion-label>
                <ion-input type="text"></ion-input>
            </ion-item>
            <ion-item>
                <ion-label>Field 6</ion-label>
                <ion-input type="text"></ion-input>
            </ion-item>
        </ion-list>
    </ion-content>`
})
export class MyPage {
    @ViewChild(Content) content: Content;

    constructor(private: keyboardService: KeyboardService) {}

    // add the keyboard scroll action to this page. this is added after the view has been created,
    // so the content element will be avaialble.
    ionViewDidEnter() {

        // timeout seems to be required, to ensure that the content child is available
        setTimeout(() => {
            // set the keyboard to auto-scroll to the focused input, when it opens
            this.keyboardService.autoKeyboardScroll(this.content);
        });
    }
}
Share:
22,801
nunoarruda
Author by

nunoarruda

Result-Oriented Front End Angular Engineer with a strong technical skill-set, attention to detail, and 17 years of experience. I have a passion for translating beautiful designs into functional user interfaces and building great web applications. I actively seek out new technologies and stay up-to-date on industry trends and advancements. Continued education has allowed me to stay ahead of the curve and deliver exceptional work to each employer I’ve worked for. I've successfully delivered projects like a CSS UI library used by 17,000 employees, a mobile app that has 120,000+ users, and a web app serving over 100 million images. I've done frontend work for Adobe, Webflow, Bayer, among other companies. I'm originally from Portugal but I've been working remotely for the last 6 years for companies worldwide. I can be flexible in order to have overlapping working hours with a distributed team.

Updated on July 09, 2022

Comments

  • nunoarruda
    nunoarruda almost 2 years

    I want my message input bar to float above the keyboard when the keyboard shows but it looks like there's no keyboard-attach directive (like v1) in Ionic 2 yet (maybe in the works?). Is there any alternative/workaround?

    Current behaviour:

    Wanted behaviour:

    Here's the code of my message input bar:

    <ion-toolbar position="bottom" *ngIf="userIsAdmin">
    
        <form (ngSubmit)="onSubmit(f)" #f="ngForm" class="message-form">
    
            <ion-badge class="message-form-badge">Admin</ion-badge>
    
            <ion-input type="text" placeholder="Type a message..." ngControl="messageInput"></ion-input>
    
            <button type="submit" small class="message-form-button">Send <ion-icon name="send"></ion-icon></button>
    
        </form>
    
    </ion-toolbar>