From a17c9f1f247e147b0ea67bca0843ba3fcf7215b5 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Mon, 16 May 2022 13:19:36 -0600 Subject: [PATCH 1/2] Allow LayoutView to pass touches to child controls when CascadeInputTransparent is false Fixes #6574 --- .../GestureManager/GestureManager.iOS.cs | 58 +++++-------------- src/Core/src/Platform/iOS/LayoutView.cs | 32 ++++++++++ 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/src/Controls/src/Core/Platform/GestureManager/GestureManager.iOS.cs b/src/Controls/src/Core/Platform/GestureManager/GestureManager.iOS.cs index 4ff0637bb9ef..e76e32c59418 100644 --- a/src/Controls/src/Core/Platform/GestureManager/GestureManager.iOS.cs +++ b/src/Controls/src/Core/Platform/GestureManager/GestureManager.iOS.cs @@ -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) @@ -418,7 +384,6 @@ void LoadRecognizers() _shouldReceiveTouch = ShouldReceiveTouch; } - CalculateUserInteractionEnabled(); UIDragInteraction? uIDragInteraction = null; UIDropInteraction? uIDropInteraction = null; @@ -526,7 +491,20 @@ 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; } @@ -534,13 +512,9 @@ bool ShouldReceiveTouch(UIGestureRecognizer recognizer, UITouch touch) // 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; } diff --git a/src/Core/src/Platform/iOS/LayoutView.cs b/src/Core/src/Platform/iOS/LayoutView.cs index 9e62b4ec6ec6..fd6d55fc0574 100644 --- a/src/Core/src/Platform/iOS/LayoutView.cs +++ b/src/Core/src/Platform/iOS/LayoutView.cs @@ -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 @@ -52,5 +54,35 @@ public override void WillRemoveSubview(UIView uiview) internal Func? CrossPlatformMeasure { get; set; } internal Func? 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; + } + } } } \ No newline at end of file From 3843a0cd1ed994358849f7e1366db5318e116f6e Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Tue, 17 May 2022 09:31:29 -0600 Subject: [PATCH 2/2] Remove tests that don't make sense anymore --- .../tests/DeviceTests/GestureTests.iOS.cs | 28 ------------------- .../Handlers/HandlerTestBaseOfT.iOS.cs | 7 +++++ 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/src/Controls/tests/DeviceTests/GestureTests.iOS.cs b/src/Controls/tests/DeviceTests/GestureTests.iOS.cs index 382e1262dfa6..bf581e129d46 100644 --- a/src/Controls/tests/DeviceTests/GestureTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/GestureTests.iOS.cs @@ -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(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(label); - Assert.True(handler.PlatformView.UserInteractionEnabled); - label.GestureRecognizers.Clear(); - Assert.False(handler.PlatformView.UserInteractionEnabled); - }); - } } } diff --git a/src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.iOS.cs b/src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.iOS.cs index c5cd12d06767..fd026ac14d97 100644 --- a/src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.iOS.cs +++ b/src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.iOS.cs @@ -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