Environment
Describe the bug
Tap events are being propagated to parent objects, even when isPassThroughParentEnabled = false;
To Reproduce
Add nested tap events to the objects, a Label with a tap inside a StackLayout with another tap is enough, the StackLayout event will be fired even when the touch is on the Label.
<ScrollView tap="test">
<StackLayout tap="test">
<Label text="Touch this label" tap="test" textWrap="true"/>
</StackLayout>
</ScrollView>
export function test(args: EventData) {
(<any>args.object).isPassThroughParentEnabled = false; // This seems to be ignored
console.log("Touch on " + args.object);
}
Expected behavior
Taps not being propagated when isPassThroughParentEnabled is set to false.
Sample project
https://play.nativescript.org/?template=play-tsc&id=pj7FjN
Tap the label and see a log for each element.
Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.
@MukaSchultze the default value of isPassThroughParentEnabled
property is false so there must be some misunderstanding of the functionality that this property enables. isPassThroughParentEnabled
is declared on LayoutBase (i.e. StackLayout, GridLayout, WrapLayout are LayoutBase but Label is not) and indicates whether touch event should pass through to a parent view of the layout container in case an interactive child view did not handle it.
For example with the following setup:
<StackLayout tap="onOuterStackTap">
<StackLayout tap="onStackTap" backgroundColor="red" height="200" isPassThroughParentEnabled="true">
<Button isUserInteractionEnabled="false" color="white" backgroundColor="green" tap="onTap" text="WILL NOT TAP"></Button>
<Button color="white" backgroundColor="blue" tap="onTap2" text="TAP"></Button>
</StackLayout>
</StackLayout>
Tap on red should "fall through" to the outer stack (execute onOuterStackTap)
Tap on green should again "fall through" to the outer stack (execute onOuterStackTap)
Tap on blue should execute onTap2
Maybe the Label functionality you are looking for can be better implemented with isEnabled
/ isUserInteractionEnabled
instead.
@manoldonev, ok, still, the expected behavior is that if you remove or set to false the isPassthroughParentEnabled on the red StackLayout, the onOuterStackTap should not be fired, right? This is not what's happening
With the code below the onOuterStackTap is still being called when tapping the red layout, in fact, both events are being called, so changing the isPassthroughParentEnabled of your example doesn't seem to change the behavior of taps at all.
<StackLayout tap="onOuterStackTap">
<StackLayout tap="onStackTap" backgroundColor="red" height="200" isPassthroughParentEnabled="false">
<Button isUserInteractionEnabled="false" color="white" backgroundColor="green" tap="onTap" text="WILL NOT TAP"></Button>
<Button color="white" backgroundColor="blue" tap="onTap2" text="TAP"></Button>
</StackLayout>
</StackLayout>
@MukaSchultze just noticed that there is a typo in the code snippet I sent to you in my last reply -- property should be isPassThroughParentEnabled
and not isPassthroughParentEnabled
(updated the snippet too).
isPassThroughParentEnabled
default value is false i.e. by default we are not tampering in any way with the standard platform hit-testing logic. For iOS this essentially means that indeed tap will be executed both for the red layout, and its outer wrapper but we are not controlling that.
If you set the property to true (see https://github.com/NativeScript/tns-core-modules-widgets/blob/2bdb7a42e23afe999ea4a56e736c3fd8d36a4bca/ios/TNSWidgets/TNSWidgets/UIView%2BPassthroughParent.m#L46) the red layout tap will not execute and the event will directly pass through to the outer wrapper.
If you run the playground on Android and tap the Label (you can tap the StackLayout too, just tap around the button), you'll see that the tap is being triggered for the scrollview aswell. isPassThroughParentEnabled
has no effect at all.
According to the docs:
Gets or sets a value indicating whether touch event should pass through to a parent view of the layout container in case an interactive child view did not handle it. Default value of this property is false. This does not affect the appearance of the view.
So if we set it to true, the touch event should pass through the parent view if a child handled it. If we set it to false, the touch event should NOT pass through the parent if a child handled it.
Currently, (at least on Android) if you tap anything, it'll call all the tap functions, regardless of this property.
@edusperoni I'd like to clarify again that if the property is set to false, we are not tampering with the hit-testing logic specific to each platform in any way -- you can see the implementation here: https://github.com/NativeScript/tns-core-modules-widgets/commit/5f34a5871f439ea4f92da9583a38fc16b2dd33ba
So if we set it to true, the touch event should pass through the parent view if a child handled it.
This assumption is not correct. If you set the property to true, the touch event will only directly pass through to a parent view of the layout container in case an interactive child view of the layout container did not handle it -- you can find the original discussion that triggered the implementation of this feature here: https://github.com/NativeScript/NativeScript/issues/6191.
I'll consider how to extend the property description as it seems it is kind of misleading at the moment.
by default we are not tampering in any way with the standard platform hit-testing logic
So maybe the problem isn't the isPassThroughParentEnabled
, but I'm certain something changed the hit test logic of NS5, the playground example behaves differently on NS4.
@manoldonev @MukaSchultze i think the change in behavior does not come from the pass through property but from the gesture handling system. in ns4 if you had a tap or swipe on a layout the gesture would not be recognized if triggered over a child. this was fixed in ns5. taps are gestures so , if I am correct, this is what your are seeing here.
Btw false is the default behavior for pass through. the idea is actually contrary to what you are doing. the idea is to prevent views from catching touches event. used mostly for things like video overlays or map action overlays
Hey @MukaSchultze,
I have just updated to NS5 and have this same annoying behaviour #3751, #4883. I think it makes perfect sense for children of a layout to intercept tap events, if the user taps within the hit area of the child. It also makes sense for the parent to respond to those tap events if the user taps outside its children, therefore in the hit area of the parent. This kind of nested hit areas is very common in UI design. On desktop frameworks such as Qt there is plenty freedom to either pass the event further up (or down in z order depending on how you look at it) to the parent or to accept the event by calling something like event.accept(true)
and to stop any further propagation. It would be nice to have something like this in NS.
Anyway you can put a hack in to ignore the tap event. Implementation in TypeScript with Angular.
declare private flag whether to ignore the tap and the corresponding getter and setter functions:
private ignoreTap_ = false;
get ignoreTap() {
if (this.ignoreTap_) {
this.ignoreTap_ = false;
return true;
}
return false;
}
set ignoreTap(flag) {
this.ignoreTap_ = flag;
}
then in ui template:
<StackLayout (tap)="!ignoreTap && onParentTap()">
<StackLayout (tap)="ignoreTap = true; onChildTap()"></StackLayout>
</StackLayout>
As you can see when the child is tapped the ignoreTap flag is set to true. Then tap event on parent is also triggered, however at this point ignoreTap is queried. If ignoreTap has been set to true it is reset to false and the onParentTap is never called. On the other hand if ignoreTap is set to false, onParentTap is called.
P.S. Thank you so much NativeScript for developing such an amazing product!
isPassThroughParentEnabled
is not working as advertised.
In fact - I'm getting the parent callback first and then the child - even the work-around will not work for me.
@gkoulin 's workaround along with setting isPassThroughParentEnabled to true should do the job, but since the parent tap is called before the child tap event, you will need to use nexttick function or something in the parent tap event to change that sequence. I have tested it and it works well.
I actually found the source if the issue here. The reason is that when you dont set a touchListener on an android view, the view is effectively viewed as "pass through". This is a default android behavior: not clickable => default onTouchEvent
returns false so it goes to the parent.
So the question is what do we want to do with this?
isUserInteractionEnabled
which is true by default. Right now setClickable
is only called if we have gestures here@rigor789 @NathanaelA @NathanWalker @edusperoni what s your view on this? i can easily create a PR once we decide what we prefer
Most helpful comment
isPassThroughParentEnabled
is not working as advertised.In fact - I'm getting the parent callback first and then the child - even the work-around will not work for me.