Reputation: 131
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:
Consider the following scenario:
So basically when releasing one finger, it acts like both fingers were removed. Am I missing something?
Upvotes: 6
Views: 2925
Reputation: 14794
It seems that this problem was fixed in iOS 4.2 (I could not reproduce it on an iPhone 4 with iOS 4.2.1)
Upvotes: 0
Reputation: 14
I have a solution, but it is not optimal. Keep track of touch.identifier as touchstart events fire. When you get a touchend event setinterval of 500ms. If during the interval another touchend and a touchstart have fired, see if the touchstart has an extant touch identifier and you can determine which finger lifted.
When two fingers are down there is no way to determine at the time of the first touchend which finger has lifted. I've looked at the order of the changedTouches, I've checked a saved copy of the touchstart events. I've looked at the gesture events that precede the glitch. The delay in the remaining finger's duplicate touchstart depends on a touchmove firing.
If this bug is busting your multi-touch interaction, go add feedback to this clearly wrong description of the event sequence on the developer docs. GestureEvent Class Reference
Upvotes: 0
Reputation: 131
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>
Upvotes: 1
Reputation: 8671
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 :)
Upvotes: 0