Skip to content

Commit

Permalink
Allow LayoutView to pass touches to child controls when CascadeInputT…
Browse files Browse the repository at this point in the history
…ransparent is false (#7236)

* Allow LayoutView to pass touches to child controls when CascadeInputTransparent is false
Fixes #6574

* Remove tests that don't make sense anymore
  • Loading branch information
hartez committed May 17, 2022
1 parent c6317a8 commit ad68647
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 70 deletions.
58 changes: 16 additions & 42 deletions src/Controls/src/Core/Platform/GestureManager/GestureManager.iOS.cs
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;
}
}
}
}
7 changes: 7 additions & 0 deletions src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.iOS.cs
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

0 comments on commit ad68647

Please sign in to comment.