How to disable iOS "Shake to Undo" in a webapp
Asked Answered
I

9

13

I'm building a game as a webapp for the iPad. It incorporates a shake action to trigger an event for one of the screens, but, since it also has a information collection form, sometimes that shake action triggers the annoying "Shake to Undo" dialog. The form input the user has typed in don't even exist in the DOM anymore when the user reaches the shake action, so in my mind the Undo function shouldn't be triggered, but unfortunately it is. There is no way this can be wrapped in a native wrapper (PhoneGap or other), so I was wondering if anyone knows if it's at all possible to disable this functionality for a specific web page/app, through CSS or JavaScript?

Thanks.

Ignaz answered 29/6, 2011 at 11:46 Comment(0)
T
4

I recently ran into this issue while developing a game where a user pairs devices using WebSockets. Unfortunately none of the solutions mentioned above work.

In short, the game involved a user subscribing to a channel via a form on their phone. Once subscribed they would shake the device and a scenario would play out on their desktop. The problem was that any time someone shook an iOS device the "undo typing" alert would pop up.

Here are the only two solutions that I've found for this issue.

  1. Prevent your input from ever losing focus. This will require you to prevent form submission if the input is part of a form. You must also listen for "touchstart" instead of "click" on any anchors and prevent the touchstart event from propagating. This will prevent that anchor from gaining focus and your input from losing focus. This approach has some disadvantages for sure.

  2. Add a "keydown" event handler to the window and prevent the event from propagating. This prevents any values from getting inserted into the input on iOS devices. I also had to create an object that mapped the keycodes to the proper character. This is the route I ended going because all I needed in my input was a 6 character code so I only needed numbers. If you need more than just numbers this may be a pain in the ass. On key down, snag that character via the keycode and set the value of the input. This tricks iOS into thinking that no value was ever inserted into the input via the keyboard so there is nothing for it to undo.

Keep in mind preventing the keydown event from propagating does not prevent input on the stock android browser, so it will just function normally. But that is OK since this is not an android issue. You can prevent duplicate values from getting inserted by listening for the input event on the input itself and removing the keydown listener if this event is received.

var codes = {
  48: 0,
  49: 1,
  50: 2,
  51: 3,
  52: 4,
  53: 5,
  54: 6,
  55: 7,
  56: 8,
  57: 9
},
myInput = document.getElementById("myInput");

var keydownHelper = function (e) {
  e.preventDefault();
  myInput.removeEventListener("input", inputHelper); 

  var val = myInput.value;

  // Delete
  if (e.keyCode === 8 && val.length) {
    myInput.value = val.slice(0, val.length - 1);
    return;
  }

  // If not a number, do nada
  if (typeof codes[e.keyCode] === "undefined") { return; }

  val += codes[e.keyCode];
  myInput.value = val;
};

var inputHelper = function (e) {
  e.preventDefault();
  window.removeEventListener("keydown", keydownHelper);
};

myInput.addEventListener("input", inputHelper);
window.addEventListener("keydown", keydownHelper); 
Tempietempla answered 24/4, 2013 at 17:17 Comment(1)
Thanks for sharing! I'm not in a position to set up a test case right now, but your answer seems to be very well researched and to do what I asked for in the original question, so I'm accepting it as the correct one if noone has any objections :)Dianthus
C
10

This blog describes a very simple shake detection using the DeviceMotionEvent listener:

http://www.jeffreyharrell.com/blog/2010/11/creating-a-shake-event-in-mobile-safari/

Try the following code to disable to undo event happening:

window.addEventListener('devicemotion', function (e) {
    // This is where you put your code for dealing with the shake event here

    // Stop the default behavior from triggering the undo dialog (hopefully)
    e.preventDefault();
});

There is one other thing I can think of which would be to turn the text input into a readonly item after you are done with it. Presumably, a read only input field cannot make use of the undo function, though I haven't tested it. It's odd that the feature fires even after the input is no longer part of the DOM.

I'm not sure if preventing a shake action can be done through the -webkit- properties in CSS. Here is a list of webkit specific CSS properties (which may not contain 100% of the properties accessible).

http://qooxdoo.org/documentation/general/webkit_css_styles

Chalet answered 29/6, 2011 at 12:42 Comment(3)
Good idea, but no luck unfortunately. Even when attaching (or using Zepto's delegate function) the event to the input elements themselves, the Undo dialog still pops up.Dianthus
Oh, and setting the input to readonly still makes the dialog pop up as well, unfortunately.Dianthus
This does not work with the current version of cordova (7.0.1). Tobias Christian Jensens answer does work perfectly. Simply add [UIApplication sharedApplication].applicationSupportsShakeToEdit = NO; into the didFinishLaunchingWithOptions function of Classes/AppDelegate.mRafiq
M
9

For PhoneGap I just inserted this line: [UIApplication sharedApplication].applicationSupportsShakeToEdit = NO; in the webViewDidFinishLoad class and it worked wonders for me.

Just go to the MainViewController.m and change webViewDidFinishLoad to look like this:

- (void)webViewDidFinishLoad:(UIWebView*)theWebView
{
    // Black base color for background matches the native apps
    theWebView.backgroundColor = [UIColor blackColor];

[UIApplication sharedApplication].applicationSupportsShakeToEdit = NO;

    return [super webViewDidFinishLoad:theWebView];
}
Masque answered 9/5, 2013 at 11:39 Comment(0)
T
4

I recently ran into this issue while developing a game where a user pairs devices using WebSockets. Unfortunately none of the solutions mentioned above work.

In short, the game involved a user subscribing to a channel via a form on their phone. Once subscribed they would shake the device and a scenario would play out on their desktop. The problem was that any time someone shook an iOS device the "undo typing" alert would pop up.

Here are the only two solutions that I've found for this issue.

  1. Prevent your input from ever losing focus. This will require you to prevent form submission if the input is part of a form. You must also listen for "touchstart" instead of "click" on any anchors and prevent the touchstart event from propagating. This will prevent that anchor from gaining focus and your input from losing focus. This approach has some disadvantages for sure.

  2. Add a "keydown" event handler to the window and prevent the event from propagating. This prevents any values from getting inserted into the input on iOS devices. I also had to create an object that mapped the keycodes to the proper character. This is the route I ended going because all I needed in my input was a 6 character code so I only needed numbers. If you need more than just numbers this may be a pain in the ass. On key down, snag that character via the keycode and set the value of the input. This tricks iOS into thinking that no value was ever inserted into the input via the keyboard so there is nothing for it to undo.

Keep in mind preventing the keydown event from propagating does not prevent input on the stock android browser, so it will just function normally. But that is OK since this is not an android issue. You can prevent duplicate values from getting inserted by listening for the input event on the input itself and removing the keydown listener if this event is received.

var codes = {
  48: 0,
  49: 1,
  50: 2,
  51: 3,
  52: 4,
  53: 5,
  54: 6,
  55: 7,
  56: 8,
  57: 9
},
myInput = document.getElementById("myInput");

var keydownHelper = function (e) {
  e.preventDefault();
  myInput.removeEventListener("input", inputHelper); 

  var val = myInput.value;

  // Delete
  if (e.keyCode === 8 && val.length) {
    myInput.value = val.slice(0, val.length - 1);
    return;
  }

  // If not a number, do nada
  if (typeof codes[e.keyCode] === "undefined") { return; }

  val += codes[e.keyCode];
  myInput.value = val;
};

var inputHelper = function (e) {
  e.preventDefault();
  window.removeEventListener("keydown", keydownHelper);
};

myInput.addEventListener("input", inputHelper);
window.addEventListener("keydown", keydownHelper); 
Tempietempla answered 24/4, 2013 at 17:17 Comment(1)
Thanks for sharing! I'm not in a position to set up a test case right now, but your answer seems to be very well researched and to do what I asked for in the original question, so I'm accepting it as the correct one if noone has any objections :)Dianthus
I
4

Objective-C

Just add the line of code inside "- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions"

[application setApplicationSupportsShakeToEdit:NO];

Swift 2.x

Add single line of code in appDelegate.swift didFinishLaunchingWithOptions method

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -Bool {
    
    application.applicationSupportsShakeToEdit = false
    return true
}

Swift 3.x

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    
    application.applicationSupportsShakeToEdit = false
    return true
}
Innis answered 5/8, 2014 at 15:19 Comment(0)
F
1

Perhaps try event.preventDefault(); in your DeviceMotionEvent listener ?

Looking around Stack Overflow, I can see other people have had success disabling default "events" using CSS (oddly).

Prevent default Press but not default Drag in iOS MobileSafari?

Farinaceous answered 29/6, 2011 at 12:6 Comment(0)
U
1

See: How to stop the UITextField from responding to the shake gesture?

For phonegap, set this property within the webViewFinishLoad method in AppDelegate.m

Ultraviolet answered 5/10, 2012 at 0:1 Comment(0)
B
1

First solution

The easiest way is to use this plugin:

https://github.com/nleclerc/cordova-plugin-ios-disableshaketoedit

Second solution

If you don't want to use the above plugin then you should do it manually by opening the MainViewController.m and change webViewDidFinishLoad to look like this:

- (void)webViewDidFinishLoad:(UIWebView*)theWebView
{
    // Black base color for background matches the native apps
    theWebView.backgroundColor = [UIColor blackColor];

[UIApplication sharedApplication].applicationSupportsShakeToEdit = NO;

    return [super webViewDidFinishLoad:theWebView];
}

Third solution

it's another solution i designed to disable "Shake To Undo" which works on text inputs. note that users are not allowed to insert emojis.

<body>
    <textarea cols="40" rows="8" name="textarea" id="textarea"></textarea>

    <script>
    textArea = document.getElementById("textarea");

    textArea.onkeypress =  function(event){
        event.preventDefault();
        var nationUnicode=event.which;
        var utf8=encodeURIComponent(String.fromCharCode(parseInt(nationUnicode, 10)));
        if  (utf8.search("%EF") != 0) //value is not an Emoji
            textArea.value= textArea.value + String.fromCharCode(nationUnicode);
    }

    textArea.onkeydown = function (event){
        if (event.keyCode == 8){
            event.preventDefault();
            var txtval = textArea.value;
            textArea.value = txtval.slice(0, - 1);
        }   
    }
    </script>
</body>
Bhopal answered 18/4, 2017 at 20:19 Comment(1)
thank you so much for solution 3! Exactly what I was looking for! Some emoji seem to be working (flash for example)Patrick
D
1

I found a workaround that works consistently: you can host the text input in a frame. When you're done with the input, remove the frame from the page. This seems to do the trick.

function resetIframe() {
  // Remove the old frame
  document.body.removeChild(frame);

  // Create a new frame that has an input internally
  frame = document.createElement('iframe');
  frame.src = '/html-with-input.html';
  frame.style = 'border: 0; height: 30px; width: 200px;'
  document.body.appendChild(frame);

  // When the frame is ready, grab a reference to the input
  frame.addEventListener('load', () => {  
    const input = frame.contentDocument.body.querySelector('input');
    console.log(frame, frame.contentDocument.body)

    // When the input changes, grab the value
    input.addEventListener('input', e => {
      document.getElementById('output').innerText = e.target.value;
    });
  });
}

Here's a proof of concept: https://codepen.io/joshduck/full/RJMYRd/

Directive answered 21/6, 2018 at 10:9 Comment(0)
T
0

Put the input into an iframe, reload the iframe when you don't need the input.

Trotter answered 15/3, 2016 at 6:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.