Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow LayoutView to pass touches to child controls when CascadeInputTransparent is false #7236

Merged
merged 2 commits into from May 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -373,40 +373,6 @@ static bool ShouldRecognizeTapsTogether(NativeGestureRecognizer gesture, NativeG
return true;
}

// This logic should all be replaced once we implement the "InputTransparent" property
// https://github.com/dotnet/maui/issues/1190
bool? _previousUserInteractionEnabled;
void CalculateUserInteractionEnabled()
{
if (ElementGestureRecognizers == null || _platformView == null || _handler?.VirtualView == null)
return;

bool hasGestureRecognizers = ElementGestureRecognizers.Count > 0;

// If no gestures have ever been added then don't do anything
if (!hasGestureRecognizers && _previousUserInteractionEnabled == null)
return;

_previousUserInteractionEnabled ??= _platformView.UserInteractionEnabled;

if (hasGestureRecognizers)
{
_platformView.UserInteractionEnabled = true;
}
else
{
_platformView.UserInteractionEnabled = _previousUserInteractionEnabled.Value;

// These are the known places where UserInteractionEnabled is modified inside Maui.Core
// Once we implement "InputTransparent" all of this should just get managed the "InputTransparent" mapper property
if (_handler.VirtualView is ITextInput)
_handler.UpdateValue(nameof(ITextInput.IsReadOnly));

_handler.UpdateValue(nameof(IView.IsEnabled));
_previousUserInteractionEnabled = null;
}
}

void LoadRecognizers()
{
if (ElementGestureRecognizers == null)
Expand All @@ -418,7 +384,6 @@ void LoadRecognizers()
_shouldReceiveTouch = ShouldReceiveTouch;
}

CalculateUserInteractionEnabled();
UIDragInteraction? uIDragInteraction = null;
UIDropInteraction? uIDropInteraction = null;

Expand Down Expand Up @@ -526,21 +491,30 @@ void LoadRecognizers()

bool ShouldReceiveTouch(UIGestureRecognizer recognizer, UITouch touch)
{
if (touch.View == _handler.PlatformView)
var platformView = _handler?.PlatformView;
var virtualView = _handler?.VirtualView;

if (virtualView == null || platformView == null)
{
return false;
}

if (virtualView.InputTransparent)
{
return false;
}

if (touch.View == platformView)
{
return true;
}

// If the touch is coming from the UIView our handler is wrapping (e.g., if it's
// wrapping a UIView which already has a gesture recognizer), then we should let it through
// (This goes for children of that control as well)
if (_handler?.PlatformView == null)
{
return false;
}

if (touch.View.IsDescendantOfView(_handler.PlatformView) &&
(touch.View.GestureRecognizers?.Length > 0 || _handler.PlatformView.GestureRecognizers?.Length > 0))
if (touch.View.IsDescendantOfView(platformView) &&
(touch.View.GestureRecognizers?.Length > 0 || platformView.GestureRecognizers?.Length > 0))
{
return true;
}
Expand Down
28 changes: 0 additions & 28 deletions src/Controls/tests/DeviceTests/GestureTests.iOS.cs
Expand Up @@ -27,33 +27,5 @@ public async Task UserInteractionEnabledTrueWhenInitializedWithGestureRecognizer
Assert.True(handler.PlatformView.UserInteractionEnabled);
});
}

[Fact]
public async Task UserInteractionEnabledSetAfterAddingGestureRecognizer()
{
var label = new Label();

await InvokeOnMainThreadAsync(() =>
{
var handler = CreateHandler<LabelHandler>(label);
label.GestureRecognizers.Add(new TapGestureRecognizer() { NumberOfTapsRequired = 1 });
Assert.True(handler.PlatformView.UserInteractionEnabled);
});
}

[Fact]
public async Task UserInteractionEnabledUnsetAfterRemovingGestureRecognizer()
{
var label = new Label();
label.GestureRecognizers.Add(new TapGestureRecognizer() { NumberOfTapsRequired = 1 });

await InvokeOnMainThreadAsync(() =>
{
var handler = CreateHandler<LabelHandler>(label);
Assert.True(handler.PlatformView.UserInteractionEnabled);
label.GestureRecognizers.Clear();
Assert.False(handler.PlatformView.UserInteractionEnabled);
});
}
}
}
32 changes: 32 additions & 0 deletions src/Core/src/Platform/iOS/LayoutView.cs
Expand Up @@ -7,6 +7,8 @@ namespace Microsoft.Maui.Platform
{
public class LayoutView : MauiView
{
bool _userInteractionEnabled;

// TODO: Possibly reconcile this code with ViewHandlerExtensions.MeasureVirtualView
// If you make changes here please review if those changes should also
// apply to ViewHandlerExtensions.MeasureVirtualView
Expand Down Expand Up @@ -52,5 +54,35 @@ public override void WillRemoveSubview(UIView uiview)

internal Func<double, double, Size>? CrossPlatformMeasure { get; set; }
internal Func<Rect, Size>? CrossPlatformArrange { get; set; }

public override UIView HitTest(CGPoint point, UIEvent? uievent)
{
var result = base.HitTest(point, uievent);

if (!_userInteractionEnabled && this.Equals(result))
{
// If user interaction is disabled (IOW, if the corresponding Layout is InputTransparent),
// then we exclude the LayoutView itself from hit testing. But it's children are valid
// hit testing targets.

return null!;
}

return result;
}

public override bool UserInteractionEnabled
{
get => base.UserInteractionEnabled;
set
{
// We leave the base UIE value true no matter what, so that hit testing will find children
// of the LayoutView. But we track the intended value so we can use it during hit testing
// to ignore the LayoutView itself, if necessary.

base.UserInteractionEnabled = true;
_userInteractionEnabled = value;
}
}
}
}
Expand Up @@ -112,6 +112,13 @@ public async Task RotationInitializeCorrectly(float rotation)
[InlineData(false)]
public async Task InputTransparencyInitializesCorrectly(bool inputTransparent)
{
if (typeof(TStub) == typeof(LayoutStub))
{
// The platform type for Layouts (LayoutView) always has UserInteractionEnabled
// to allow for its children to be interacted with
return;
}

var view = new TStub()
{
InputTransparent = inputTransparent
Expand Down