Issue
I have an Ionic 4 / Angular cordova based application (Ionic info at end of post).
I am currently using Cordova windows 6.0.1
due to this issue. All seems to work fine except for this what this question is about.
When I select an input field on a Windows Surface tablet, the keyboard covers the input field, ie the application does not reduce it’s height as it does on iOS
and Android
(it works fine on these other two platforms).
When I view what is happening on a working Android device (using chrome://inspect
), I notice the height of the element with selector ion-appmd ion-page hydrated
is reduced from 616p
x to 325px
.
I have not found a way to do the same debugging on remote Windows device, so I can’t investigate this, however I assume the above is just not happening.
I found this post which makes sense to me as it looks to be adjusting the height of ion-app
. I have copied that code, (got rid of the jquery
), but my problem is that the keyboardDidShow
event just does not seem to fire when I invoke the keyboard on the surface (I Have logging I send to the server so I can see this is not happening).
So my (similar) code is as follows…
const ionApp = document.getElementsByTagName("ion-app")[0];
this.logger.info(ionApp ? "got app" : "not got app");
window.addEventListener('keyboardDidShow', async (event) => {
this.logger.info('keyboardDidShow'); // <----- don't even get to here
// Move ion-app up, to give room for keyboard
let kbHeight: number = event["keyboardHeight"];
let viewportHeight: number = window.innerHeight;
this.logger.info(`viewportHeight: ${viewportHeight}`);
const focusedElement = document.activeElement;
if (!focusedElement) {
this.logger.warn('windowsKeyboardWorkaround: Could not get focused input');
return;
}
let inputFieldOffsetFromBottomViewPort: number = viewportHeight - focusedElement.getBoundingClientRect().bottom;
let inputScrollPixels = kbHeight - inputFieldOffsetFromBottomViewPort;
// Set margin to give space for native keyboard.
ionApp.style["margin-bottom"] = kbHeight.toString() + "px";
// But this diminishes ion-content and may hide the input field...
if (inputScrollPixels > 0) {
const content = document.getElementsByTagName('ion-content');
if (!content || content.length == 0)
return;
const topMostContent = content[content.length - 1];
let ionScroll = await topMostContent.getScrollElement();
setTimeout(() => {
ionScroll.animate({
scrollTop: ionScroll.scrollTop + inputScrollPixels
}, 300);
}, 300); // Matches scroll animation from css.
}
});
window.addEventListener('keyboardDidHide', () => {
// Move ion-app down again
// Scroll not necessary.
ionApp.style["margin-bottom"] = "0px";
});
Does anyone have any idea about this event, and why it is not firing for me, or if there is a way to look at all events on window (or other) to see if there is a keyboard event at all being raised?
Thanks in advance for any info.
[UPDATE1]
I forgot to remove this code when running on Android, and I noticed that the keyboardDidShow
IS being raised here (ie on Android, where of course I don’t need it). So it just appears to be on Windows, or at least the surface tablet I have to test.
[UPDATE2]
It looks like the keyboardDidHide
event comes from the cordova keyboard plugin, where is says only iOS and Android are supported. Sure enough, if we go to the source, I see only ios
and android
…
So I need some other way…
[UPDATE3]
Elsewhere in my App, I hide the WIndows back button using the following….
let w: any = window;
if (w.cordova != undefined && w.cordova.platformId == "windows") {
let currentView = w.Windows.UI.Core.SystemNavigationManager.getForCurrentView();
currentView.appViewBackButtonVisibility = w.Windows.UI.Core.AppViewBackButtonVisibility.collapsed;
}
So this means we have access to Windows.UI.Core
, and I assume everything in this namespace.
So far I can’t see anything to hook into the the keyboard showing/hiding.
In a UWP
example from here under Windows-universal-samples\Samples\TouchKeyboard\cs
I see the following in Scenario2_ShowHideEvents.xaml.cs
…
protected override void OnNavigatedTo(NavigationEventArgs e)
{
InputPane currentInputPane = InputPane.GetForCurrentView();
// Subscribe to Showing/Hiding events
currentInputPane.Showing += OnShowing;
currentInputPane.Hiding += OnHiding;
}
But not sure where to get this from the Windows.UI.Core
..
Ionic info
Ionic:
Ionic CLI : 5.2.3 (C:\Users\peter\AppData\Roaming\npm\node_modules\ionic)
Ionic Framework : @ionic/angular 4.11.3
@angular-devkit/build-angular : 0.802.0
@angular-devkit/schematics : 8.2.0
@angular/cli : 8.2.0
@ionic/angular-toolkit : 2.0.0
Cordova:
Cordova CLI : 9.0.0 ([email protected])
Cordova Platforms : android 8.1.0, windows 6.0.1
Cordova Plugins : cordova-plugin-ionic-keyboard 2.1.3, cordova-plugin-ionic-webview 4.1.3, (and 12 other plugins)
Utility:
cordova-res : 0.7.0-testing.0
native-run : 0.2.8
System:
Android SDK Tools : 26.1.1 (C:\Users\peter\AppData\Local\Android\sdk)
NodeJS : v10.15.3 (C:\Program Files\nodejs\node.exe)
npm : 6.4.1
OS : Windows 10
Solution
I have come up with a solution, though not 100% perfect as it does not get the exact keyboard height. I just divide the viewport in half, but is it way better than nothing.
I’ll just paste my whole class (the couple of util helpers will be easy to work out what they do)
import { Injectable } from '@angular/core';
import { Utils } from '@shared/utils';
import { Logger } from './logger.service';
/**
* Manages the Windows soft keyboard showing / hiding and resizing the app when it is shown/hidden
*/
@Injectable()
export class WindowsKeyboardService {
/**
* Construction
* @param logger - logger
*/
constructor(private logger: Logger) { }
/**
* Call from main component once the view is visible, so we can get access to Windows Input pane
* and hook up the soft keyboard handlers
*/
public async hookupKeyboardHandlers() : Promise<void> {
try {
this.logger.info('hookupKeyboardHandlers');
// Only need this for Windows. For ioS/Android the keyboard plugin does all this for us.
if (!await Utils.isWindows()) {
this.logger.info('WindowsKeyboardService.hookupKeyboardHandlers - not windows. Skipping');
return;
}
let w = <any>window;
const inputPane = w.Windows.UI.ViewManagement.InputPane.getForCurrentView();
if (!inputPane) {
this.logger.error('WindowsKeyboardService.hookupKeyboardHandlers: could not get inputPane');
return;
}
inputPane.addEventListener('showing', _ => this.onWindowsKeyboardUp);
inputPane.addEventListener('hiding', _ => this.onWindowsKeyboardClose);
} catch (error) {
this.logger.error(`WindowsKeyboardService.hookupKeyboardHandlers: ${error}`)
}
}
/**
* Raised when a Windows soft keyboard is opened
*/
private onWindowsKeyboardUp() : void {
try {
this.logger.info("onWindowKeyboardUp");
// Just half viewportHeight for now (until can find out how to get keyboard height - if even possible)
let viewportHeight: number = window.innerHeight;
let kbHeight = viewportHeight / 2;
this.logger.info(`viewportHeight: ${viewportHeight}`);
const focusedElement = document.activeElement;
if (!focusedElement) {
this.logger.info('WindowsKeyboardService.onWindowsKeyboardUp: Could not get focused input');
return;
}
let inputFieldOffsetFromBottomViewPort: number = viewportHeight - focusedElement.getBoundingClientRect().bottom;
let inputScrollPixels = kbHeight - inputFieldOffsetFromBottomViewPort;
const ionApp = document.getElementsByTagName("ion-app")[0];
this.logger.info(ionApp ? "got app" : "not got app");
// Set margin to give space for native keyboard.
ionApp.style["margin-bottom"] = kbHeight.toString() + "px";
// But this diminishes ion-content and may hide the input field...
if (inputScrollPixels > 0) {
const content = document.getElementsByTagName('ion-content');
if (!content || content.length == 0)
return;
const topMostContent = content[content.length - 1];
setTimeout(async () => {
let ionScroll = await topMostContent.getScrollElement();
ionScroll.animate({
scrollTop: ionScroll.scrollTop + inputScrollPixels
}, 300);
}, 300); // Matches scroll animation from css.
}
} catch (error) {
this.logger.error(`WindowsKeyboardService.onWindowKeyboardUp: ${error}`);
}
}
/**
* Raised when a Windows soft keyboard is closed
*/
private onWindowsKeyboardClose(): void {
try {
this.logger.info("WindowsKeyboardService.onWindowKeyboardClose");
const ionApp = document.getElementsByTagName("ion-app")[0];
ionApp.style["margin-bottom"] = "0";
} catch (error) {
this.logger.error(`WindowsKeyboardService.onWindowKeyboardClose: ${error}`);
}
}
}
And I hook it up in the app.component..
public async ngAfterViewInit(): Promise<void> {
try {
this.logger.info('AppComponent: ngAfterViewInit');
this.windowsKeyboardService.hookupKeyboardHandlers();
} catch (error) {
this.logger.error(`AppComponent: ngAfterViewInit: ${error}`);
}
}