JKennedy
JKennedy

Reputation: 18799

Is it possible to hook into Hardware keyboard events at an Application level?

I am developing a Xamarin.Forms application for iOS, Android and UWP.

I would like to hook into hardware keyboard events at an application level (not an individual page level (because its a Xamarin.Forms application)) so that when a user presses a key on the keyboard I can invoke an action in my application.

On Android I did this using the following event on MainActivity:.

public override bool OnKeyUp([GeneratedEnum] Keycode keyCode, KeyEvent e)
{
    //process key press
    return base.OnKeyUp(keyCode, e);
}

Is there an equivilent for Xamarin iOS?

Upvotes: 9

Views: 5769

Answers (4)

Petar_FX
Petar_FX

Reputation: 11

I did this way: in MainActivity.cs:

public override bool OnKeyUp([GeneratedEnum] Keycode keyCode, KeyEvent e)
    {
        if (keyCode == Keycode.VolumeDown)
        {
            var vm = MainViewModel.GetInstance().YourPageName;
            if (vm != null && App.Navigator.CurrentPage.GetType().Name == "YourPagePage")
            {
                vm.GuardarRecorrido();
                return true;
            }
        }

        return base.OnKeyUp(keyCode, e);
    }

I asked for the name so volume key only runs in that page.

Upvotes: 1

Kevin Li
Kevin Li

Reputation: 2258

You can use the KeyCommands to track the key pressing from hardware keyboard.

keyCommands

A responder object that supports hardware keyboard commands can redefine this property and use it to return an array of UIKeyCommand objects that it supports. Each key command object represents the keyboard sequence to recognize and the action method of the responder to call in response.

The key commands you return from this method are applied to the entire responder chain. When an key combination is pressed that matches a key command object, UIKit walks the responder chain looking for an object that implements the corresponding action method. It calls that method on the first object it finds and then stops processing the event.

In Xamarin.forms, you have to create custom renderer for ContentPage at iOS platform. Then the keycommands can be added in that page renderer.

If you want the key presses can be handled by the ViewController(Page), not just the input controls(such as Entry), use canBecomeFirstResponder to enable viewcontroller to become a first responder.

For example, the custom renderer of ContentPage in iOS platform could like this:

using System;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using KeyCommandsInXamarinForms.iOS;

[assembly: ExportRenderer(typeof(ContentPage), typeof(MyCustomPageRenderer))]
namespace KeyCommandsInXamarinForms.iOS
{
    public class MyCustomPageRenderer : PageRenderer
    {
        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null || Element == null)
            {
                return;
            }

            //Create your keycommand as you need.
            UIKeyCommand keyCommand1 = UIKeyCommand.Create(new NSString("1"), UIKeyModifierFlags.Command, new ObjCRuntime.Selector("Action:"));
            UIKeyCommand keyCommand2 = UIKeyCommand.Create(new NSString("\t"), 0, new ObjCRuntime.Selector("Action:"));
            //Add your keycommands
            this.AddKeyCommand(keyCommand1);
            this.AddKeyCommand(keyCommand2);
        }

        [Export("Action:")]
        private void Excute(UIKeyCommand keyCommand)
        {
            Console.WriteLine(String.Format("key pressed - {0}", keyCommand.Value);
        }


        //Enable viewcontroller to become the first responder, so it is able to respond to the key commands.
        public override bool CanBecomeFirstResponder
        {
            get
            {
                return true;
            }
        }
    }
}

Notice this line, using typeof(ContentPage) as the handler parameter, then you don't need to change anything in your PCL:

[assembly: ExportRenderer(typeof(ContentPage), typeof(MyCustomPageRenderer))]

Upvotes: 7

Scott R. Frost
Scott R. Frost

Reputation: 2061

I needed to allow numeric input + an enter at the end in my XamForms app. Here's how I solved it in Xamarin Forms 3.0.0.482510 / Xamarin.iOS 11.10 (it's a little different than Kevin's answer above because I wanted to handle it in the XamForms shared xaml project, not in the iOS project):

In your iOS project (with a Xamarin Forms shared project in the solution named 'MobileProject' for example), create a new class:

using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(MobileProject.MainPage), typeof (com.YourCompany.iOS.KeyboardHookRenderer))]
namespace com.YourCompany.iOS
{
public class KeyboardHookRenderer : PageRenderer
{
    private string _RecvValue = string.Empty;

    public override bool CanBecomeFirstResponder
    {
        get { return true; }
    }

    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        base.OnElementChanged(e);
        string key = string.Empty;
        var selector = new ObjCRuntime.Selector("KeyRecv:");
        UIKeyCommand accelerator1 = UIKeyCommand.Create((NSString)"1", 0, selector);
        AddKeyCommand(accelerator1);
        UIKeyCommand accelerator2 = UIKeyCommand.Create((NSString)"2", 0, selector);
        AddKeyCommand(accelerator2);
        UIKeyCommand accelerator3 = UIKeyCommand.Create((NSString)"3", 0, selector);
        AddKeyCommand(accelerator3);

        ... etc as many as you need or use a loop based on key id...
    }

    [Export("KeyRecv:")]
    public void KeyRecv(UIKeyCommand cmd)
    {
        if (cmd == null)
            return;
        var inputValue = cmd.Input;
        if (inputValue == "\n" || inputValue == "\r")
        {
            ((MobileProject.MainPage) Element)?.HandleHardwareKeyboard(_RecvValue);
            _RecvValue = string.Empty;
        }
        else
        {
            _RecvValue += inputValue;
        }
    }
}
}

Then, in your shared app in MainPage.xaml.cs, you just need:

/// <summary>
/// Handle hardware keys (from KeyboardHookRender.cs in iOS project)
/// </summary>
/// <param name="keys">Keys sent, including trailing Cr or Lf</param>
public void HandleHardwareKeyboard(string keys)
{
    SomeTextbox.Text = keys;
    // Whatever else you need to do to handle it
}

Upvotes: 3

Taier
Taier

Reputation: 2119

The thing with iOS is that it does not have such method. At first, you need to understand what buttons you want to capture. If you want to capture Volume buttons, then you can find the answer here: https://forums.xamarin.com/discussion/29648/detect-volume-change-from-hardware-buttons

public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();

        //KVO for outputVolume
        var session = AVAudioSession.SharedInstance();
        var errorOrNull = session.SetActive (true);
        if (errorOrNull != null) {
            //TODO: ...handle it
        }
        session.AddObserver (this, "outputVolume", NSKeyValueObservingOptions.New, IntPtr.Zero);


    }

    public override void ObserveValue (NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
    {
                   //TODO: Filter as appropriate, error-handling, etc.
        var volume = (float) (change ["new"] as NSNumber);

        volumeLabel.Text = volume.ToString();
    }

If you want to capture keyboard events, you have to subscribe to a view that is responsible for an input process.

You can use a NotificationCenter to subscribe to all views that can handle input and receive notifications in one place.

NSNotificationCenter.DefaultCenter.AddObserver (UITextField.TextFieldTextDidChangeNotification, (notification) =>
{
    Console.WriteLine ("Character received! {0}", notification.Object == TextField);
});

Upvotes: 1

Related Questions