|
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Threading.Tasks;
using CoreAnimation;
using CoreGraphics;
using Foundation;
using Microsoft.Maui.Graphics;
using UIKit;
using static Microsoft.Maui.Primitives.Dimension;
namespace Microsoft.Maui.Platform
{
public static partial class ViewExtensions
{
internal const string BackgroundLayerName = "MauiBackgroundLayer";
public static void UpdateIsEnabled(this UIView platformView, IView view)
{
if (platformView is not UIControl uiControl)
return;
uiControl.Enabled = view.IsEnabled;
}
public static void Focus(this UIView platformView, FocusRequest request)
{
request.TrySetResult(platformView.BecomeFirstResponder());
}
public static void Unfocus(this UIView platformView, IView view)
{
platformView.ResignFirstResponder();
}
public static void UpdateVisibility(this UIView platformView, IView view) =>
ViewExtensions.UpdateVisibility(platformView, view.Visibility);
public static void UpdateVisibility(this UIView platformView, Visibility visibility)
{
switch (visibility)
{
case Visibility.Visible:
platformView.Inflate();
platformView.Hidden = false;
break;
case Visibility.Hidden:
platformView.Inflate();
platformView.Hidden = true;
break;
case Visibility.Collapsed:
platformView.Hidden = true;
platformView.Collapse();
break;
}
}
public static void UpdateBackground(this ContentView platformView, IBorderStroke border)
{
bool hasShape = border.Shape != null;
if (hasShape)
{
platformView.UpdateMauiCALayer(border);
}
}
public static void UpdateBackground(this UIView platformView, IView view)
{
platformView.UpdateBackground(view.Background, view as IButtonStroke);
}
public static void UpdateBackground(this UIView platformView, Paint? paint, IButtonStroke? stroke = null)
{
// Remove previous background gradient layer if any
platformView.RemoveBackgroundLayer();
if (paint.IsNullOrEmpty())
{
if (platformView is LayoutView)
platformView.BackgroundColor = null;
else
return;
}
if (paint is SolidPaint solidPaint)
{
Color backgroundColor = solidPaint.Color;
if (backgroundColor == null)
platformView.BackgroundColor = ColorExtensions.BackgroundColor;
else
platformView.BackgroundColor = backgroundColor.ToPlatform();
return;
}
else if (paint is GradientPaint gradientPaint)
{
var backgroundLayer = gradientPaint?.ToCALayer(platformView.Bounds);
if (backgroundLayer != null)
{
backgroundLayer.Name = BackgroundLayerName;
platformView.BackgroundColor = UIColor.Clear;
backgroundLayer.UpdateLayerBorder(stroke);
platformView.InsertBackgroundLayer(backgroundLayer, 0);
}
}
}
public static void UpdateFlowDirection(this UIView platformView, IView view)
{
UISemanticContentAttribute updateValue = platformView.SemanticContentAttribute;
switch (view.FlowDirection)
{
case FlowDirection.MatchParent:
updateValue = GetParentMatchingSemanticContentAttribute(view);
break;
case FlowDirection.LeftToRight:
updateValue = UISemanticContentAttribute.ForceLeftToRight;
break;
case FlowDirection.RightToLeft:
updateValue = UISemanticContentAttribute.ForceRightToLeft;
break;
}
if (updateValue != platformView.SemanticContentAttribute)
{
platformView.SemanticContentAttribute = updateValue;
if (view is ITextAlignment)
{
// A change in flow direction may mean a change in text alignment
view.Handler?.UpdateValue(nameof(ITextAlignment.HorizontalTextAlignment));
}
PropagateFlowDirection(updateValue, view);
}
}
static UISemanticContentAttribute GetParentMatchingSemanticContentAttribute(IView view)
{
var parent = view.Parent?.Handler?.PlatformView as UIView;
if (parent == null)
{
// No parent, no direction we need to match
return UISemanticContentAttribute.Unspecified;
}
var parentSemanticContentAttribute = parent.SemanticContentAttribute;
if (parentSemanticContentAttribute == UISemanticContentAttribute.ForceLeftToRight
|| parentSemanticContentAttribute == UISemanticContentAttribute.ForceRightToLeft)
{
return parentSemanticContentAttribute;
}
// The parent view isn't using an explicit direction, so there's nothing for us to match
return UISemanticContentAttribute.Unspecified;
}
static void PropagateFlowDirection(UISemanticContentAttribute semanticContentAttribute, IView view)
{
if (semanticContentAttribute != UISemanticContentAttribute.ForceLeftToRight
&& semanticContentAttribute != UISemanticContentAttribute.ForceRightToLeft)
{
// If the current view isn't using an explicit LTR/RTL value, there's nothing to propagate
return;
}
// If this view has any child/content views, we'll need to call UpdateFlowDirection on them
// because they _may_ need to update their FlowDirection to match this view
if (view is IContainer container)
{
foreach (var child in container)
{
if (child.Handler?.PlatformView is UIView uiView)
{
uiView.UpdateFlowDirection(child);
}
}
}
else if (view is IContentView contentView
&& contentView.PresentedContent is IView child)
{
if (child.Handler?.PlatformView is UIView uiView)
{
uiView.UpdateFlowDirection(child);
}
}
}
public static void UpdateOpacity(this UIView platformView, IView view) => platformView.UpdateOpacity(view.Opacity);
internal static void UpdateOpacity(this UIView platformView, double opacity) => platformView.Alpha = (float)opacity;
public static void UpdateAutomationId(this UIView platformView, IView view) =>
platformView.AccessibilityIdentifier = view.AutomationId;
public static void UpdateClip(this UIView platformView, IView view)
{
if (platformView is WrapperView wrapper)
wrapper.Clip = view.Clip;
}
public static void UpdateShadow(this UIView platformView, IView view)
{
var shadow = view.Shadow;
var clip = view.Clip;
// If there is a clip shape, then the shadow should be applied to the clip layer, not the view layer
if (clip == null)
{
if (shadow == null)
platformView.ClearShadow();
else
platformView.SetShadow(shadow);
}
else
{
if (platformView is WrapperView wrapperView)
wrapperView.Shadow = view.Shadow;
}
}
[Obsolete("IBorder is not used and will be removed in a future release.")]
public static void UpdateBorder(this UIView platformView, IView view)
{
var border = (view as IBorder)?.Border;
if (platformView is WrapperView wrapperView)
wrapperView.Border = border;
}
internal static T? GetChildAt<T>(this UIView view, int index) where T : UIView
{
if (index < view.Subviews.Length)
return (T?)view.Subviews[index];
return null;
}
public static T? FindDescendantView<T>(this UIView view) where T : UIView =>
FindDescendantView<T>(view, (_) => true);
[Obsolete("MAUI background layers now automatically update their Frame when their SuperLayer Frame changes. This method will be removed in a future release.")]
public static void UpdateBackgroundLayerFrame(this UIView view) =>
view.UpdateBackgroundLayerFrame(BackgroundLayerName);
[Obsolete("MAUI background layers now automatically update their Frame when their SuperLayer Frame changes. This method will be removed in a future release.")]
internal static void UpdateBackgroundLayerFrame(this UIView view, string layerName)
{
if (view.Frame.IsEmpty)
{
return;
}
var layer = view.Layer;
if (layer?.Sublayers is { Length: > 0 } sublayers)
{
var bounds = view.Bounds;
UpdateBackgroundLayers(sublayers, layerName, bounds);
}
}
static void UpdateBackgroundLayers(this CALayer[] layers, string layerName, CGRect bounds)
{
foreach (var layer in layers)
{
if (layer.Sublayers is { Length: > 0 } sublayers)
{
UpdateBackgroundLayers(sublayers, layerName, bounds);
}
if (layer.Name == layerName && layer.Frame != bounds)
{
layer.Frame = bounds;
}
}
}
public static void InvalidateMeasure(this UIView platformView, IView view)
{
platformView.SetNeedsLayout();
// MauiView/WrapperView already propagates the SetNeedsLayout to the parent
if (platformView is not MauiView && platformView is not WrapperView)
{
platformView.Superview?.SetNeedsLayout();
}
}
public static void UpdateWidth(this UIView platformView, IView view)
{
UpdateFrame(platformView, view);
}
public static void UpdateHeight(this UIView platformView, IView view)
{
UpdateFrame(platformView, view);
}
public static void UpdateMinimumHeight(this UIView platformView, IView view)
{
UpdateFrame(platformView, view);
}
public static void UpdateMaximumHeight(this UIView platformView, IView view)
{
UpdateFrame(platformView, view);
}
public static void UpdateMinimumWidth(this UIView platformView, IView view)
{
UpdateFrame(platformView, view);
}
public static void UpdateMaximumWidth(this UIView platformView, IView view)
{
UpdateFrame(platformView, view);
}
public static void UpdateFrame(UIView platformView, IView view)
{
if (!IsExplicitSet(view.Width) || !IsExplicitSet(view.Height))
{
// Ignore the initial setting of the value; the initial layout will take care of it
return;
}
// Updating the frame (assuming it's an actual change) will kick off a layout update
// Handling of the default width/height will be taken care of by GetDesiredSize
var currentFrame = platformView.Frame;
platformView.Frame = new CoreGraphics.CGRect(currentFrame.X, currentFrame.Y, view.Width, view.Height);
}
public static async Task UpdateBackgroundImageSourceAsync(this UIView platformView, IImageSource? imageSource, IImageSourceServiceProvider? provider)
{
if (provider == null)
return;
if (imageSource != null)
{
var service = provider.GetRequiredImageSourceService(imageSource);
var scale = platformView.GetDisplayDensity();
var result = await service.GetImageAsync(imageSource, scale);
var backgroundImage = result?.Value;
if (backgroundImage == null)
return;
platformView.BackgroundColor = UIColor.FromPatternImage(backgroundImage);
}
}
public static int IndexOfSubview(this UIView platformView, UIView subview)
{
if (platformView.Subviews.Length == 0)
return -1;
return Array.IndexOf(platformView.Subviews, subview);
}
public static UIImage? ConvertToImage(this UIView view)
{
var imageRenderer = new UIGraphicsImageRenderer(view.Bounds.Size);
return imageRenderer.CreateImage((a) =>
{
view.Layer.RenderInContext(a.CGContext);
});
}
public static UINavigationController? GetNavigationController(this UIView view)
{
var rootController = view.Window?.RootViewController;
if (rootController is UINavigationController nc)
return nc;
return rootController?.NavigationController;
}
internal static void Collapse(this UIView view)
{
// See if this view already has a collapse constraint we can use
foreach (var constraint in view.Constraints)
{
if (constraint is CollapseConstraint collapseConstraint)
{
// Active the collapse constraint; that will squish the view down to zero height
collapseConstraint.Active = true;
return;
}
}
// Set up a collapse constraint and turn it on
var collapse = new CollapseConstraint();
view.AddConstraint(collapse);
collapse.Active = true;
}
internal static bool Inflate(this UIView view)
{
// Find and deactivate the collapse constraint, if any; the view will go back to its normal height
foreach (var constraint in view.Constraints)
{
if (constraint is CollapseConstraint collapseConstraint)
{
collapseConstraint.Active = false;
return true;
}
}
return false;
}
public static void ClearSubviews(this UIView view)
{
for (int n = view.Subviews.Length - 1; n >= 0; n--)
{
view.Subviews[n].RemoveFromSuperview();
}
}
internal static Rect GetPlatformViewBounds(this IView view)
{
var platformView = view?.ToPlatform();
if (platformView == null)
{
return new Rect();
}
return platformView.GetPlatformViewBounds();
}
internal static Rect GetPlatformViewBounds(this UIView platformView)
{
if (platformView == null)
return new Rect();
var superview = platformView;
while (superview.Superview is not null)
{
superview = superview.Superview;
}
var convertPoint = platformView.ConvertRectToView(platformView.Bounds, superview);
var X = convertPoint.X;
var Y = convertPoint.Y;
var Width = convertPoint.Width;
var Height = convertPoint.Height;
return new Rect(X, Y, Width, Height);
}
internal static Matrix4x4 GetViewTransform(this IView view)
{
var platformView = view?.ToPlatform();
if (platformView == null)
return new Matrix4x4();
return platformView.Layer.GetViewTransform();
}
internal static Matrix4x4 GetViewTransform(this UIView view)
=> view.Layer.GetViewTransform();
internal static Point GetLocationOnScreen(this UIView view) =>
view.GetPlatformViewBounds().Location;
internal static Point? GetLocationOnScreen(this IElement element)
{
if (element.Handler?.MauiContext == null)
return null;
return (element.ToPlatform())?.GetLocationOnScreen();
}
internal static Graphics.Rect GetBoundingBox(this IView view)
=> view.ToPlatform().GetBoundingBox();
internal static Graphics.Rect GetBoundingBox(this UIView? platformView)
{
if (platformView == null)
return new Rect();
var nvb = platformView.GetPlatformViewBounds();
var transform = platformView.GetViewTransform();
var radians = transform.ExtractAngleInRadians();
var rotation = CoreGraphics.CGAffineTransform.MakeRotation((nfloat)radians);
CGAffineTransform.CGRectApplyAffineTransform(nvb, rotation);
return new Rect(nvb.X, nvb.Y, nvb.Width, nvb.Height);
}
internal static Rect GetFrameRelativeTo(this UIView view, UIView relativeTo)
{
var viewWindowLocation = view.GetLocationOnScreen();
var relativeToLocation = relativeTo.GetLocationOnScreen();
return
new Rect(
new Point(viewWindowLocation.X - relativeToLocation.X, viewWindowLocation.Y - relativeToLocation.Y),
new Graphics.Size(view.Bounds.Width, view.Bounds.Height)
);
}
internal static UIView? GetParent(this UIView? view)
{
return view?.Superview;
}
internal static Size LayoutToMeasuredSize(this IView view, double width, double height)
{
var size = view.Measure(width, height);
var platformFrame = new CGRect(0, 0, size.Width, size.Height);
if (view.Handler is IPlatformViewHandler viewHandler && viewHandler.PlatformView != null)
viewHandler.PlatformView.Frame = platformFrame;
view.Arrange(platformFrame.ToRectangle());
return size;
}
public static void UpdateInputTransparent(this UIView platformView, IViewHandler handler, IView view)
{
// Interaction should not be disabled for an editor if it is set as read-only
// because this prevents users from scrolling the content inside an editor.
if (view is not IEditor && view is ITextInput textInput)
{
platformView.UpdateInputTransparent(textInput.IsReadOnly, view.InputTransparent);
return;
}
platformView.UserInteractionEnabled = !view.InputTransparent;
}
public static void UpdateInputTransparent(this UIView platformView, bool isReadOnly, bool inputTransparent)
{
platformView.UserInteractionEnabled = !(isReadOnly || inputTransparent);
}
internal static UIToolTipInteraction? GetToolTipInteraction(this UIView platformView)
{
UIToolTipInteraction? interaction = default;
if (OperatingSystem.IsMacCatalystVersionAtLeast(15)
|| OperatingSystem.IsIOSVersionAtLeast(15))
{
if (platformView is UIControl control)
{
interaction = control.ToolTipInteraction;
}
else
{
if (platformView.Interactions is not null)
{
foreach (var ia in platformView.Interactions)
{
if (ia is UIToolTipInteraction toolTipInteraction)
{
interaction = toolTipInteraction;
break;
}
}
}
}
}
return interaction;
}
public static void UpdateToolTip(this UIView platformView, ToolTip? tooltip)
{
// UpdateToolTips were added in 15.0 for both iOS and MacCatalyst
if (OperatingSystem.IsMacCatalystVersionAtLeast(15)
|| OperatingSystem.IsIOSVersionAtLeast(15))
{
string? text = tooltip?.Content?.ToString();
var interaction = platformView.GetToolTipInteraction();
if (interaction is null)
{
if (!string.IsNullOrEmpty(text))
{
interaction = new UIToolTipInteraction(text);
platformView.AddInteraction(interaction);
}
}
else
{
interaction.DefaultToolTip = text;
}
}
}
internal static IWindow? GetHostedWindow(this IView? view)
=> GetHostedWindow(view?.Handler?.PlatformView as UIView);
internal static IWindow? GetHostedWindow(this UIView? view)
=> GetHostedWindow(view?.Window);
internal static bool IsLoaded(this UIView uiView)
{
if (uiView == null)
return false;
return uiView.Window != null;
}
internal static IDisposable OnLoaded(this UIView uiView, Action action)
{
if (uiView.IsLoaded())
{
action();
return new ActionDisposable(() => { });
}
var observers = new List<IDisposable>(2);
ActionDisposable? disposable = null;
disposable = new ActionDisposable(() =>
{
disposable = null;
foreach (var observer in observers)
{
observer.Dispose();
}
observers.Clear();
});
// Ideally we could wire into UIView.MovedToWindow but there's no way to do that without just inheriting from every single
// UIView. So we just make our best attempt by observering some properties that are going to fire once UIView is attached to a window.
if (uiView is IUIViewLifeCycleEvents lifeCycleEvents)
{
lifeCycleEvents.MovedToWindow += OnLifeCycleEventsMovedToWindow;
observers.Add(new ActionDisposable(() =>
{
lifeCycleEvents.MovedToWindow -= OnLifeCycleEventsMovedToWindow;
}));
void OnLifeCycleEventsMovedToWindow(object? sender, EventArgs e)
{
//The MovedToWindow fires multiple times during navigation animations, causing repeated OnLoadedCheck calls.
//BeginInvokeOnMainThread ensures OnLoadedCheck executes after all window transitions are complete.
uiView.BeginInvokeOnMainThread(() => OnLoadedCheck(null));
}
}
else
{
var boundKey = new NSString("bounds");
var frameKey = new NSString("frame");
var boundObserver = (NSObject)uiView.Layer.AddObserver(boundKey, Foundation.NSKeyValueObservingOptions.OldNew, (oc) => OnLoadedCheck(oc));
var frameObserver = (NSObject)uiView.Layer.AddObserver(frameKey, Foundation.NSKeyValueObservingOptions.OldNew, (oc) => OnLoadedCheck(oc));
observers.Add(new ActionDisposable(() => uiView.Layer.RemoveObserver(boundObserver, boundKey)));
observers.Add(new ActionDisposable(() => uiView.Layer.RemoveObserver(frameObserver, frameKey)));
}
// OnLoaded is called at the point in time where the xplat view knows it's going to be attached to the window.
// So this just serves as a way to queue a call on the UI Thread to see if that's enough time for the window
// to get attached.
uiView.BeginInvokeOnMainThread(() => OnLoadedCheck(null));
void OnLoadedCheck(NSObservedChange? nSObservedChange = null)
{
if (disposable is not null)
{
if (uiView.IsLoaded())
{
disposable.Dispose();
disposable = null;
action();
}
else if (nSObservedChange != null)
{
// In some cases (FlyoutPage) the arrange and measure all take place before
// the view is added to the screen so this queues up a second check that
// hopefully will fire loaded once the view is added to the window.
// None of this code is great but I haven't found a better way
// for an outside observer to know when a subview is added to a window
uiView.BeginInvokeOnMainThread(() => OnLoadedCheck(null));
}
}
};
return disposable;
}
internal static IDisposable OnUnloaded(this UIView uiView, Action action)
{
if (!uiView.IsLoaded())
{
action();
return new ActionDisposable(() => { });
}
var observers = new List<IDisposable>(2);
ActionDisposable? disposable = null;
disposable = new ActionDisposable(() =>
{
disposable = null;
foreach (var observer in observers)
{
observer.Dispose();
}
observers.Clear();
});
// Ideally we could wire into UIView.MovedToWindow but there's no way to do that without just inheriting from every single
// UIView. So we just make our best attempt by observering some properties that are going to fire once UIView is attached to a window.
if (uiView is IUIViewLifeCycleEvents lifeCycleEvents)
{
lifeCycleEvents.MovedToWindow += OnLifeCycleEventsMovedToWindow;
observers.Add(new ActionDisposable(() =>
{
lifeCycleEvents.MovedToWindow -= OnLifeCycleEventsMovedToWindow;
}));
void OnLifeCycleEventsMovedToWindow(object? sender, EventArgs e)
{
//The MovedToWindow fires multiple times during navigation animations, causing repeated UnLoadedCheck calls.
//BeginInvokeOnMainThread ensures UnLoadedCheck executes after all window transitions are complete.
uiView.BeginInvokeOnMainThread(UnLoadedCheck);
}
}
// OnUnloaded is called at the point in time where the xplat view knows it's going to be detached from the window.
// So this just serves as a way to queue a call on the UI Thread to see if that's enough time for the window
// to get detached.
uiView.BeginInvokeOnMainThread(UnLoadedCheck);
void UnLoadedCheck()
{
if (!uiView.IsLoaded() && disposable != null)
{
disposable.Dispose();
disposable = null;
action();
}
};
return disposable;
}
internal static void UpdateLayerBorder(this CoreAnimation.CALayer layer, IButtonStroke? stroke)
{
if (stroke == null)
return;
if (stroke.StrokeColor != null)
layer.BorderColor = stroke.StrokeColor.ToCGColor();
if (stroke.StrokeThickness >= 0)
layer.BorderWidth = (float)stroke.StrokeThickness;
if (stroke.CornerRadius >= 0)
layer.CornerRadius = stroke.CornerRadius;
}
internal static T? FindResponder<T>(this UIView view) where T : UIResponder
{
var nextResponder = view as UIResponder;
while (nextResponder is not null)
{
nextResponder = nextResponder.NextResponder;
if (nextResponder is T responder)
return responder;
}
return null;
}
internal static T? FindResponder<T>(this UIViewController controller) where T : UIViewController
{
var nextResponder = controller.View as UIResponder;
while (nextResponder is not null)
{
nextResponder = nextResponder.NextResponder;
if (nextResponder is T responder && responder != controller)
return responder;
}
return null;
}
internal static T? FindTopController<T>(this UIView view) where T : UIViewController
{
var bestController = view.FindResponder<T>();
var tempController = bestController;
while (tempController is not null)
{
tempController = tempController.FindResponder<T>();
if (tempController is not null)
bestController = tempController;
}
return bestController;
}
internal static UIView? FindNextView(this UIView? view, UIView containerView, Func<UIView, bool> isValidType)
{
UIView? nextView = null;
while (view is not null && view != containerView && nextView is null)
{
var siblings = view.Superview?.Subviews;
if (siblings is null)
break;
// TableView and ListView cells may not be in order so handle separately
if (view.FindResponder<UITableView>() is UITableView tableView)
{
nextView = view.FindNextInTableView(tableView, isValidType);
if (nextView is null)
view = tableView;
}
else
nextView = view.FindNextView(siblings.IndexOf(view) + 1, isValidType);
view = view.Superview;
}
// if we did not find the next view, try to find the first one
nextView ??= containerView.Subviews?[0]?.FindNextView(0, isValidType);
return nextView;
}
static UIView? FindNextView(this UIView? view, int index, Func<UIView, bool> isValidType)
{
// search through the view's siblings and traverse down their branches
var siblings = view is UITableView table ? table.VisibleCells : view?.Superview?.Subviews;
if (siblings is null)
return null;
for (int i = index; i < siblings.Length; i++)
{
var sibling = siblings[i];
if (!sibling.Hidden && sibling.Subviews?.Length > 0)
{
var childVal = sibling.Subviews[0].FindNextView(0, isValidType);
if (childVal is not null)
return childVal;
}
if (isValidType(sibling))
return sibling;
}
return null;
}
static UIView? FindNextInTableView(this UIView view, UITableView table, Func<UIView, bool> isValidType)
{
if (isValidType(view))
{
var index = view.FindTableViewCellIndex(table);
return index == -1 ? null : table.FindNextView(index + 1, isValidType);
}
return null;
}
static int FindTableViewCellIndex(this UIView view, UITableView table)
{
var cells = table.VisibleCells;
var viewCell = view.FindResponder<UITableViewCell>();
for (int i = 0; i < cells.Length; i++)
{
if (cells[i] == viewCell)
return i;
}
return -1;
}
internal static void ChangeFocusedView(this UIView view, UIView? newView)
{
if (newView is null)
view.ResignFirstResponder();
else
newView.BecomeFirstResponder();
}
internal static UIView? GetContainerView(this UIView? startingPoint)
{
var rootView = startingPoint?.FindResponder<ContainerViewController>()?.View;
if (rootView is not null)
return rootView;
var firstViewController = startingPoint?.FindTopController<UIViewController>();
if (firstViewController?.ViewIfLoaded is not null)
return firstViewController.ViewIfLoaded.FindDescendantView<ContentView>();
return null;
}
internal static UIView? FindFirstResponder(this UIView? superview)
{
if (superview is null)
return null;
if (superview.IsFirstResponder)
return superview;
foreach (var subview in superview.Subviews)
{
var subviewFirstResponder = subview.FindFirstResponder();
if (subviewFirstResponder is not null)
return subviewFirstResponder;
}
return null;
}
internal static float GetDisplayDensity(this UIView? view) =>
view?.Window?.GetDisplayDensity() ?? 1.0f;
internal static bool HideSoftInput(this UIView inputView) => inputView.ResignFirstResponder();
internal static bool ShowSoftInput(this UIView inputView) => inputView.BecomeFirstResponder();
internal static bool IsSoftInputShowing(this UIView inputView) => inputView.IsFirstResponder;
internal static bool IsFinalMeasureHandledBySuperView(this UIView? view) => view?.Superview is ICrossPlatformLayoutBacking { CrossPlatformLayout: not null };
}
}
|