Search Unity

iOS Repeated Key Lag

Discussion in 'iOS and tvOS' started by cryptozoologist, Jan 15, 2021.

  1. cryptozoologist

    cryptozoologist

    Joined:
    Oct 12, 2017
    Posts:
    2
    For some reason, unless I wait approximately a half a second, my iOS builds won't register repeated presses of the same key.

    Like, if I press the space bar over and over, only the first press is registered. This doesn't happen in editor, and it doesn't happen with touch input - only when a keyboard is connected to the iOS device.

    I've tried:
    Using the new Input System
    Using Rewired
    Upping the Input Actions per second in the Standalone Input Module
    Making the repeat delay 0 and 0.0001 in the Standalone Input Module
    Keeping a boolean flag to flip back and forth when key pressed/released.
    Updating the project to 2020 from 2019.

    It's like there's an axis that is resetting too slowly, but I can't figure out how to make it reset more quickly. To be specific - this is while using "Input.GetKeyDown([ANY_KEYCODE])", not using axis' or anything like that.
     
  2. glangor

    glangor

    Joined:
    Mar 24, 2013
    Posts:
    15
    I've been experiencing this exact issue with the latest 2021LTS version and a 2020 version also. Did you manage to find a resolution?

    On this page (https://docs.unity3d.com/ScriptReference/Input.GetKey.html) it says:

    iOS, tvOS: Due platform limitations, GetKeyUp event for keyboard events is delayed by about half a second, see UnityView+Keyboard.mm in the generated Xcode project for more information.

    In that file it says:

    // Keyboard shortcuts don't provide events for key up
    // Keyboard shortcut callbacks are called with 0.4 (first time) and 0.1 (following times) seconds interval while pressing the key
    // Below we implement key expiration mechanism where key up event is generated if shortcut callback
    // is not called for specific key for more than <kKeyTimeoutInSeconds>

    It sounds like when you hit a different key to the previous, the code deduces the prior keyup and triggers it. If you hit the same one a second time within that half second it doesn't know its been hit a second time. Once the timeout elapses, it triggers the keyup and is ready for that character to be hit again.

    What can be done about this? My app relies centrally on typing, so I really need to solve this. The fact it's in the docs suggests it's not a trivial workaround or they'd have done it...
     
  3. glangor

    glangor

    Joined:
    Mar 24, 2013
    Posts:
    15
  4. keystagefun

    keystagefun

    Joined:
    Feb 19, 2020
    Posts:
    33
    Did anyone find a solution to this issue? Having the exact same problem and it's a right pain. I tried all the above too before finding this thread! Thanks in advance.
     
  5. glangor

    glangor

    Joined:
    Mar 24, 2013
    Posts:
    15
    Hi keystagefun, unfortunately not. It's a crazy thing. In my game (which was mainly for a limited audience) I worked around it by ensuring I didn't use words with two repeating chars, like 'book' or 'need'. Quite a workaround, when you're teaching people to type... :)

    The underlying issue was resolved on 24 March 2020 in ios 13.4, as I noted above. So the hack can be removed, but ideally unity would do it. The fallback, since it's part of the generated project source, is that someone with skills in xcode could create a 'patched' version which could be copied into the xcode project before compilation.

    If anyone does create such an override file, please post it here!
     
  6. keystagefun

    keystagefun

    Joined:
    Feb 19, 2020
    Posts:
    33
    Thanks for the quick response.

    All I've done is open the UnityView+Keyboard.mm file generated in my build and changed the following line from:

    Code (CSharp):
    1. static const double kKeyTimeoutInSeconds = 0.5;
    to

    Code (CSharp):
    1. static const double kKeyTimeoutInSeconds = 0;
    This allowed me to type the same character repeatedly and all seems to be working fine.

    Assume for pre-iOS13.4 it would be an issue though? Will test and see what solution I can come up with for handling earlier versions.

    EDIT: this didn't solve the initial lag you get when first pressing the key, only subsequent presses. To solve that, I removed the line

    Code (CSharp):
    1. if (nowTime - item->second > kKeyTimeoutInSeconds)
    I think actually with that removed, some of the other code is redundant, as I suspect I'm just adding key presses to an array only to remove them immediately, but I don't understand enough about what's going on in the .mm file to confidently mess with anything else. Hopefully someone with a bit more knowledge can flag up what else can be removed or what needs keeping for pre iOS13.4.
     
    Last edited: Aug 31, 2022
    shaunysaurus and DemonStudios like this.
  7. DemonStudios

    DemonStudios

    Joined:
    Oct 15, 2012
    Posts:
    3
    This bug has been driving me mad for many months.

    So I recently changed my game to use the new Unity InputSystem assuming it'd surely be fixed but no - still can't press the same key again on physical keyboards within about 1/2 second on Android or iOS builds.

    I need this for a word game on iOS, which now of course also runs on Apple Silicon Macs (which all have keyboards) so being able to type at any speed (which works great on the dedicated desktop builds) is essential.

    I finally came across this thread - so thanks keystagefun - changing kKeyTimeoutInSeconds does indeed seem to magically fix it!

    Strangely for me though setting it to 0 made the keyboard not work at all - but setting it to 0.01 worked a treat.

    I have no idea why it would be desirable to not allow repeat keypresses within 0.5s.

    Please Unity fix this!
     
  8. keystagefun

    keystagefun

    Joined:
    Feb 19, 2020
    Posts:
    33
    So I'm still struggling with this...

    The solution above works for almost all scenarios when typing normally, because by setting the kKeyTimeoutInSeconds value to a very low number (ie 0.01), it fires the "fake" key up / release event almost immediately after the key down / press event, which resets the key and allows it to be pressed again.

    Additionally, if the key is held down, because the fake key up / release event has been fired immediately after the key down event, the key will keep firing the press / release cycle over and over.

    For some instances (like holding down the back space key for example) this may create the desired outcome.

    But the one situation I can't for the life of me figure out how to work around is holding down a key and then releasing it a little while later and telling the difference between the fake release / key up events that are being fired after kKeyTimeoutInSeconds has passed and my real key up event when I actually take my finger off the key.

    This is important in my situation, because I'm wanting to something to occur when the user pressed a particular key (the shift key in my case) and something else to occur when they release the key for real.

    The problem is that by the time they release the key for real, the app thinks it's already been released because a fake release event occurred after kKeyTimeoutInSeconds and that event is indistinguishable from a real key release by the user.

    I've tried absolutely everything I can think of. New input system, old input system, checking onGUI events, in the Update loop, combining both, checking at the end of each frame, using a timer to check every n milliseconds. But because the fake release event is in no way distinguishable from a real, human-activated release event, the app thinks the key has been released when it hasn't.

    What is so frustrating is that running the app in the editor, the functionality works absolutely perfectly. It's only once we transfer to an iPad that the issue occurs, so clearly it's possible to handle keyboard input correctly and as expected.

    As DemonStudios says, why on earth would you want to be forced to wait half a second by default to be able to use the same key twice in a row? And even if you did want to, why is there also no way of telling when a real key release event occurs.

    Having some difference passed through for fake / real events would solve my issue as I could then ignore the fake events.

    Unity - is there anything you can do on this, or is it an Apple thing that you have no control over?

    Any advice / further thoughts (or if anyone has implemented a successful press / hold / release for the shift key whilst pressing another key at the same time to capitalise it, I would really appreciate knowing about it - as I say, I have it working perfectly in the editor, but on iOS I'm at a brick wall).

    Thanks in advance!

    Ian
     
  9. glangor

    glangor

    Joined:
    Mar 24, 2013
    Posts:
    15
    Have a look at my earlier posts - this was written by unity to address ios limitations back in the day, but that limitation was fixed by apple in ios 13.4 so should be fixable now. It's been a while since I looked at this, but I believe if you fiddle with a generated file in the exported project you can now fix this; someone posting after me did so I believe.
     
  10. keystagefun

    keystagefun

    Joined:
    Feb 19, 2020
    Posts:
    33
    Thanks for responding. Yes - I've done all that. It was me you mention who posted after you.

    The issue I'm having now is how can you distinguish between a genuine key release (not press) and the "fake" key release that is fired automatically after kKeyTimeoutInSeconds (the value you have to fiddle with in the UnityView+Keyboard.mm generated file you mention).

    It's all good if you just want to know when a key has been pressed, because you can ignore whether it's then been released or not.

    But if you want to work with holding a key down for a length of time (and knowing it's being continuously held down), this becomes an issue, because the key thinks it's been released after whatever value is set for kKeyTimeoutInSeconds, so there is no way of knowing when the user actually releases it.

    I may have found a solution using the new input system for my particular case, but that doesn't solve the issue above, on which I'm totally stumped!
     
  11. glangor

    glangor

    Joined:
    Mar 24, 2013
    Posts:
    15
    Yep, its annoying and disappointing, I guess it shows that most unity games use touch inputs and only rarely the keys. Given how long its been resolved on the ios side though, it'd be great to see this one fixed up by the Unity team.
     
  12. andrewceco

    andrewceco

    Joined:
    Jun 6, 2020
    Posts:
    1
    I've been working on the same exact thing. What was your solution?

    Thrilled to find this thread, and I'm hoping it works for MOST things, but I also have a key I want to hold down to "charge up" an attack. Did you find a way to make this work AND have it be responsive?
     
  13. keystagefun

    keystagefun

    Joined:
    Feb 19, 2020
    Posts:
    33
    Sorry - only just saw this (3 months later - apologies!!)

    Short answer... no - on iOS I've yet to find a way of distinguishing between a real key release and the fake key release produced by the code in the UnityView+Keyboard.mm file.

    WebGL and Android builds work fine with external keyboard.

    I've managed to get round it on iOS by not using key releases for anything crucial, but it's a massive pain.

    My current setup is using a value of 0.1 for kKeyTimeoutInSeconds for key presses that trigger gameplay (I never need to trigger anything more frequently than this) and use the new input system's OnTextInput method for getting regular text input for text entry, as that seems to bypass all of this. The issue with that is that some keys (like pressing Shift for example) don't seem to be traceable from OnTextInput (as they treat shift as a secondary key that capitalises a letter, or accesses a secondary character, so what you get if you press shift + 7 is "text input" of the ampersand (&) sign.

    I've avoided the issue by not using key releases - it's only a problem for me still in that I want to offer the option of tabbing back to a previous input field by using tab + shift. The only way I could figure to manage that with the new input system was to know if shift was held down or not at the moment the user pressed the tab key. I was figuring this out by using shiftKey.wasPressedThisFrame to set it as "pressed" and then shiftKey.wasReleasedThisFrame to set it as being "not pressed". But of course after 0.1 seconds, the "fake" key release fires on iOS and shift is deemed to be not pressed any more, so when I hit tab, it thinks shift isn't down and instead tabs forwards instead of backwards.

    I can't believe something that should be so simple is so horribly complicated, but it's perfectly possible there is a solution that I can't fathom that is right in front of me!

    Did you find a way round it?
     
  14. igorskugar

    igorskugar

    Joined:
    Aug 17, 2015
    Posts:
    14
    Hi,

    I've solved the issue on my own by editing the "UnityView+Keyboard.mm" file.

    Full working solution here:
    https://gist.github.com/igorskugar/b5fc0a493339c8628446131c24445d14

    Solution is based on iOS project generated by Unity v. 2022.2.0b5.
    I've commented out most of the previous logic, but not everything can be commented out.
    Apple and Unity both have their own key codes different than ASCII codes that I'm used to, so I had to "translate" Apple codes to Unity codes.

    There are 4 pieces of code added:
    1. import at the top
    2. keyboard variable
    3. addKeyboardListener method
    4. "if" to call the method above

    Tested iOS build on:
    1. iPad Pro 2020 4th gen (wired Apple keyboard, I don't have wireless one)
    2. MBP 2021 16" M1 Max (both built-in and wired Apple keyboard)

    I cannot test it on the iPhone because I don't have wireless keyboard nor USB-Lightning adapter.
    On the iPad, the game seamlessly switches between the touch controls, wired keyboard and wireless controller (DualShock4).
    I'm using Rewired for input management.

    Known issues:
    1. If you disconnect the keyboard during the game and connect it back, the keyboard will not react anymore.
    Some connect/disconnect listener can probably solve the issue.
    2. Some of the keyboard keys still need to be added to the list (long if-else block)
    3. I don't know the language used in the file, so it may be poorly optimized.

    If it was helpful to you, please consider voting for one of the other issues reported by me:
    https://issuetracker.unity3d.com/is...-in-macos-player-when-vsynccount-is-2-or-more

    Enjoy!
     
    Last edited: Mar 12, 2023
    shaunysaurus and Fonserbc like this.
  15. Hapatus

    Hapatus

    Joined:
    Sep 9, 2015
    Posts:
    1
    It was very helpful indeed and the timing was perfect ! had been working on the issue myself over the past few days and my solution was passable but janky. I saw your post just as I was about to wrap things up and your solution works so much better. You've got my vote! FYI my Unity project is on 2021.3.18f1 and the solution seems to work as it should.

    It definitely would still be nice if Unity fixes this on their end though!
     
  16. igorskugar

    igorskugar

    Joined:
    Aug 17, 2015
    Posts:
    14
    Hah, I'm glad it worked for you too!
    Thanks for your vote, much appreciated :D

    Yeah, the GCKeyboard class is 2,5 years old at this point so it's about time.
     
  17. igorskugar

    igorskugar

    Joined:
    Aug 17, 2015
    Posts:
    14
    shaunysaurus likes this.
  18. keystagefun

    keystagefun

    Joined:
    Feb 19, 2020
    Posts:
    33
    You're a legend. Thanks for doing this. Brilliant!!!
     
    igorskugar likes this.
  19. igorskugar

    igorskugar

    Joined:
    Aug 17, 2015
    Posts:
    14
    If not for your work and research on it I wouldn't even know where to start, so great team effort! :D