Search code examples
cordovaionic-frameworkuwp

Ionic Cordova Windows 10 - How to detect soft keyboard


I have an Ionic / Cordova application which I (also) build for for Windows 10 (UWP).

On a tablet (eg Microsoft surface), I want to be able to shrink the application when the soft keyboard is invoked.

I have not found a direct way of doing this, so as a work around, I do this on an input focus/blur events, however, I would at least like to be able to determine whether or not a soft keyboard is being used, so I do NOT do this when running on a desktop.

Here is my attempt (which does not work)

    public static isUsingWindowSoftKeyboard(): boolean {
        try {
          Utils.logger.info('isUsingWindowSoftKeyboard begin');
          if (!Utils.isWindows())
            return false;
          
          let w: any = window;
          let touchCapabilities = new w.Windows.Devices.Input.TouchCapabilities();
          
          let ss = stringify(touchCapabilities);
          Utils.logger.info(ss);
          let keyboardCapabilities = new w.Windows.Devices.Input.KeyboardCapabilities();
          
          ss = stringify(keyboardCapabilities);
          Utils.logger.info(ss);

          Utils.logger.info('isUsingWindowSoftKeyboard end')

          return true;
        } catch (error) {
          Utils.logger.error(`Utils.isUsingWindowSoftKeyboard: ${error}`);
          return false;
        }
      }

In the above when I log out the ss variable (the only way I seem to be able to get any debug info on Windows), it is just empty, so I assume the call to TouchCapabilities() and KeyboardCapabilities() is just not working.

How might I be able to do what I describe above?

Edit 1

I didn't quite go far enough. Even though stringify does not show anything, at least one of the properties are actually there (perhaps they are actually getters).

Anyway, the following DID return 1 for keyboard when running on a desktop..

 let keyboardCapabilities = new w.Windows.Devices.Input.KeyboardCapabilities();
 Utils.logger.info(`Keyboard present: ${keyboardCapabilities.keyboardPresent}`);

I also tried touch capabilities properties...

let touchCapabilities = new w.Windows.Devices.Input.TouchCapabilities();      
Utils.logger.info(`touch present: ${touchCapabilities.TouchPresent}`);
Utils.logger.info(`Contacts: ${touchCapabilities.Contacts}`);

These both did report undefined, but it does show that the property touchCapabilities IS defined, otherwise I would have got a null exception when trying to access the two properties.

I now just need to test this on a tablet with a touch screen (which I can do when back in the work office where I have a surface tablet).

The one other avenue I need to re-investigate is the use of getForCurrentView() as in the following..

  let w: any = window;
  let vm = w.Windows.UI.ViewManagement;
  this.logger.info(`vm is ${(vm ? "defined" : "undefined")}`);

  let ip = vm.InputPane;
  this.logger.info(`ip is ${(ip ? "defined" : "undefined")}`);

  let inputPane = vm.InputPane.getForCurrentView();
  this.logger.info(`inputPane is ${(inputPane ? "defined" : "undefined")}`);
  
  if (inputPane) {
    inputPane.addEventListener('show', async ev => {
      this.logger.info(`inputPane show fired !!!`);
    })
  }

I did not get the above to work, but I do see getForCurrentView() mentioned in other searches, even in the context of winjs. Perhaps I was calling in the wrong place, so will try again. If this works, it would be better, as using the show and 'hide' events I could then resize the app even if the user manually closes the soft keyboard rather than relying on an inputs focus and blur.


Solution

  • Here is what worked for me. I may as well add the whole class. The "gist" of it is in the two listeners added to w.Windows.UI.ViewManagement.InputPane.getForCurrentView()

    I added the following service, and I call it from the main component ngAfterViewInit()

        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}`);
            }
          }
        }
    

    isWindows has the following...

    public static async isWindows(): Promise<boolean> {
      await Utils.platform.ready();     
      const result = window.cordova != undefined && window.cordova.platformId == "windows";
      return result;
    }
    

    And I call it as follows form the main app component..

    public async ngAfterViewInit(): Promise<void> {
      try {
        this.logger.info('AppComponent: ngAfterViewInit');
        this.windowsKeyboardService.hookupKeyboardHandlers();
      } catch (error) {
        this.logger.error(`AppComponent: ngAfterViewInit: ${error}`);
      }
    }