views:

217

answers:

3

Hi,

In a UIWebView (multi-touch enabled) I got a page with two divs (div1 and div2), each of them having registered for the touchstart and touchend events. Everytime they are receiving touch events, I am dumping the content of:

  • event.touches: contains all current touches
  • event.targetTouches: contains touches relevant to the given target
  • event.changedTouches: contains touches having triggered the event

Consider the following scenario:

  • tap on div1: event.touches contains div1
  • without releasing div1, tap on div2: event.touches contains div1 and div2
  • release div2 but not div1: event.touches is empty, it should not as div1 is still being pressed . Also div1 received a touchend event as if it had been released
  • wait a while, still having div1 pressed, and it will receive a new touchstart event, which doesn't make sense as it has never been released.

So basically when releasing one finger, it acts like both fingers were removed. Am I missing something?

A: 

Yes I have found this too. Sounds like it should be a bug, but I'm not sure.

The most flexible way I have found to do all sorts of multi-touch goodness in iOs webkit is to capture touch events on the entire document, i.e. call document.addEventListener() for all varieties of touch event you're interested in.

Then use some tactic to figure out on which element the touch happened. You can:

  • Examine the touch's target property to get some info about the element. However (another possible bug?), you can't find out the element's ID from this property, only its class, which is no good for distinguishing between multiple elements using the same class. Examine the source of this JS virtual light table for iOs to see the this in action.

  • Compare the touch's pageX and pageY co-ordinates, to the dimensions and positions of each of the elements the touch could relate to (relative to the document). For example, using jQuery:

    var x = touch.pageX;
    var y = touch.pageY;
    var offset=$(element).offset();
    if (x >= offset.left && y >= offset.top && x < (offset.left + $(element).width()) && y < (offset.top + $(element).height())) 
    {
        // element was touched!
    }
    

Using this method, all touch actions behave entirely like you'd expect :)

funkybro
If I understand it correctly, this doesn't help in my case: I want to track two independent touches on one single element.
mar10
This will help you with that; you can track as many touches as you like on the entire page, over as many elements as you like.
funkybro
yep, but as I mention into the other answer, as soon as you lift one finger, you will not know which finger has been released as all your elements will receive a touchend even with empty touches. If this does not matter for the usage you want to make of multi-touch then this is fine, if it does matter, then forget webkit as long as this bug is in. It looks like you want to track touches on the same element so you should be fine.
Oli
Indeed, so you need to change your approach: treat the entire document as one element, and don't listen for touches on a per-element basis.
funkybro
+1  A: 

Thanks funkybro for your comment but unfortunately I can stil observe the same erroneous behavior when intercepting touch events at the document level. Here is a trace of what is happening:

finger 1 touches elem1:
20:44:00.130 onTouchStart: 
    touches len=1 (elem1)
    changedTouches len=1 (elem1)

finger 2 touches elem2 (finger 1 still presses elem1 and has not been released):
20:44:01.066  onTouchStart: 
    touches len=2 (elem1,elem2)
    changedTouches len=1 (elem2)

finger 2 being released (finger 1 still presses elem1 and has not been released):
this is where things begin to go wrong: we receive two touchend events consecutively for
both elem1 and elem2,even though finger 1 is still holding on elem1 and has never released it.
Also the event.touches array is empty for both events, which is wrong since elem1 is still
being pressed.
20:44:08.241  onTouchEnd: touches len=0
              changedTouches len=1 (elem1)

20:44:08.251  onTouchEnd: touches len=0
              changedTouches len=1 (elem2)

after 4 seconds in the same position (finger 1 pressed on elem1, finger 2 released),
we receive a new touchstart event, as if the system wanted to undo the previous mistake
and put things back into a consistent state.   
20:44:12.511  onTouchStart: 
    touches len=1 (elem1)
    changedTouches len=1 (elem1)

now releasing finger 1 from elem1, we receive the touchend event
20:44:14.751  onTouchEnd: 
    touches len=0 
    changedTouches len=1 (elem1)

Edit: Here is a code sample, to run on Safari Mobile or inside your own UIWebView on device (not simulator).

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Multi-touch test</title>
    <style type="text/css">
        div.square {
            position:absolute;
            width:80px;
            height:80px;
            opacity:0.5;
        }
        div#elem1 {
            left:50px;
            top:50px;
            background-color:red;
        }
        div#elem2 {
            left:200px;
            top:50px;
            background-color:green;
        }
        textarea#logger {
            position:absolute;
            width100%;
            height:70%;
            top:30%;
            background-color:grey;
            color:white;
            overflow: scroll;
        }
    </style>

    <script type="text/javascript">
    function log(text) {
        logger.value = logger.value + text;
        logger.scrollTop = logger.scrollHeight;
    }

    function touchesDumpStr(touches,logPrefix) {
        var str = logPrefix + ', count=' + touches.length + ':';
        for (var i=0; i<touches.length; ++i) {
            if (typeof touches[i].target.customName != 'undefined') {
                str += touches[i].target.customName +' ';
            }
        }
        str += '\n';
        return str;
    }

    function onTouchStart(e) {
        log('onTouchStart\n');
        log(touchesDumpStr(e.touches, 'touches'));
        log(touchesDumpStr(e.targetTouches, 'targetTouches'));
        log(touchesDumpStr(e.changedTouches, 'changedTouches'));
        for (var i=0; i<e.changedTouches.length; ++i) {
            e.changedTouches[i].target.style.opacity=1.0;
        }
        e.preventDefault();
    }

    function onTouchEnd(e) {
        log('onTouchEnd\n');
        log(touchesDumpStr(e.touches, 'touches'));
        log(touchesDumpStr(e.targetTouches, 'targetTouches'));
        log(touchesDumpStr(e.changedTouches, 'changedTouches'));
        for (var i=0; i<e.changedTouches.length; ++i) {
            e.changedTouches[i].target.style.opacity=0.5;
        }
        e.preventDefault();
    }

    var logger;
    function init() {
        logger = document.getElementById('logger');
        document.getElementById('elem1').customName='elem1';
        document.getElementById('elem2').customName='elem2';
        document.addEventListener("touchstart", onTouchStart, false);
        document.addEventListener("touchend", onTouchEnd, false);   
    }
    </script>
</head>
<body onload="init();">
    <div class="square" id="elem1"></div>
    <div class="square" id="elem2"></div>
    <textarea id="logger" rows="10" cols="45" readonly></textarea>
</body>
</html>
Oli
This should ideally be either an addition to your original answer, or a response to my comment, not an answer in itself.
funkybro
right, sorry for this...
Oli
I don't quite understand your debugging, but it sounds like you still have listeners attached to each individual element, this is what's causing the behaviour you observe. Can you paste short relevant snippets of actual code into your question?
funkybro
I added a code sample
Oli
In the code sample above, I have also tried to overlay a transparent div covering all the screen to reduce the number of touchend event received. But as soon as one finger is lifted, event.touches gets emptied as if the two fingers were lifted...looks like a desperate case.
Oli
Some more detail I've noticed: After the 2 touchend events have fired and e.touches.length is 0. If, as you describe, finger 1 is pressed on elem1 again. The touch.identifier is the same as the first touch on elem1? I think this must be a bug because it seriously hinders the usefulness of the multi touch features!
ad rees
A: 

Oli, Did you end up finding a solution? Because I'm having the same issue and it's driving me crazy. Wasted almost a day and a half on this.

I've been using one overlay image taking up the whole screen for my tests just like you tried, and just like you I get an empty touches list, and weird firing of touch start events as well on fingers that already down on the the overlay div I've created.

Please say you've solved this. Like you said, it's basically useless without this working correctly. I don't see how the suggestion to track clientX/Y positions would help in any way.

endergen
sorry no solution yet, let's hope this will get fixed in a future OS update.
Oli