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

Fixes #18204 border lagging behind content on iOS and improves initial render performance #23156

Merged
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue18204.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue18204"
Title="Issue18204">

<VerticalStackLayout Padding="24">
<Border
HorizontalOptions="Center"
x:Name="TheBorder"
BackgroundColor="LightBlue"
Shadow="{Shadow Brush=Black, Offset='0,2', Radius=2, Opacity=0.20}"
StrokeShape="{RoundRectangle CornerRadius=20}">
<Button x:Name="TheButton" Clicked="ButtonClicked" BackgroundColor="LightGreen" WidthRequest="200" HeightRequest="500" />
</Border>
</VerticalStackLayout>
</ContentPage>
25 changes: 25 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue18204.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace Maui.Controls.Sample.Issues;

[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 18204, "[iOS] Drawing of Borders lags behind other elements creating bizarre overlaps and glitches", PlatformAffected.iOS)]

public partial class Issue18204 : ContentPage
{
public Issue18204()
{
InitializeComponent();
}

private void ButtonClicked(object sender, EventArgs e)
{
var button = (Button)sender;
button.CancelAnimations();
var targetHeight = button.HeightRequest == 200.0 ? 500.0 : 200.0;
button.Animate("Height", new Animation(v => button.HeightRequest = v, button.Height, targetHeight, Easing.Linear));
}
}
11 changes: 0 additions & 11 deletions src/Core/src/Handlers/Border/BorderHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,5 @@ static partial void UpdateContent(IBorderHandler handler)
platformView.AddSubview(platformContent);
}
}

public override void PlatformArrange(Rect rect)
{
// Disable the animation during arrange for the Border; otherwise, all resizing actions
// will animate, and it makes the Border lag behind its content.

CATransaction.Begin();
CATransaction.AnimationDuration = 0;
base.PlatformArrange(rect);
CATransaction.Commit();
}
}
}
65 changes: 54 additions & 11 deletions src/Core/src/Platform/iOS/StrokeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using CoreAnimation;
using CoreGraphics;
using Microsoft.Maui.Graphics;
Expand Down Expand Up @@ -105,13 +106,15 @@ internal static void UpdateMauiCALayer(this UIView platformView, IBorderStroke?
{
CALayer? backgroundLayer = platformView.Layer as MauiCALayer;

var initialRender = false;
if (backgroundLayer == null)
{
backgroundLayer = platformView.Layer?.Sublayers?
.FirstOrDefault(x => x is MauiCALayer);

if (backgroundLayer == null)
{
initialRender = true;
backgroundLayer = new MauiCALayer
{
Name = ViewExtensions.BackgroundLayerName
Expand All @@ -122,6 +125,13 @@ internal static void UpdateMauiCALayer(this UIView platformView, IBorderStroke?
}
}

// While we're in the process of connecting the handler properties will not change
// So it's useless to update the layer many times with the same value
if (platformView is ContentView { View: null } && !initialRender)
{
return;
}

if (backgroundLayer is MauiCALayer mauiCALayer)
{
backgroundLayer.Frame = platformView.Bounds;
Expand Down Expand Up @@ -151,26 +161,59 @@ internal static void UpdateMauiCALayer(this UIView platformView, IBorderStroke?

internal static void UpdateMauiCALayer(this UIView view)
{
if (view == null || view.Frame.IsEmpty)
if (view.Frame.IsEmpty)
{
return;
}

var layer = view.Layer;

UpdateBackgroundLayer(layer, view.Bounds);
if (layer?.Sublayers is { Length: > 0 } sublayers)
{
var bounds = view.Bounds;
var backgroundLayers = GetBackgroundLayersNeedingUpdate(sublayers, bounds);
backgroundLayers.UpdateBackgroundLayers(bounds);
}
}

static void UpdateBackgroundLayer(this CALayer layer, CGRect bounds)
static IEnumerable<CALayer> GetBackgroundLayersNeedingUpdate(this CALayer[] layers, CGRect bounds)
{
var sublayers = layer?.Sublayers;
if (sublayers is not null)
foreach (var layer in layers)
{
foreach (var sublayer in sublayers)
if (layer.Sublayers is { Length: > 0 } sublayers)
{
UpdateBackgroundLayer(sublayer, bounds);
foreach (var sublayer in GetBackgroundLayersNeedingUpdate(sublayers, bounds))
{
yield return sublayer;
}
}

if (sublayer.Name == ViewExtensions.BackgroundLayerName && sublayer.Frame != bounds)
sublayer.Frame = bounds;
if (layer.Name == ViewExtensions.BackgroundLayerName && layer.Frame != bounds)
{
yield return layer;
}
}
}

static void UpdateBackgroundLayers(this IEnumerable<CALayer> backgroundLayers, CGRect bounds)
{
using var backgroundLayerEnumerator = backgroundLayers.GetEnumerator();

if (backgroundLayerEnumerator.MoveNext())
{
// iOS by default adds animations to certain actions such as layer resizing (setting the Frame property).
// This can result in the background layer not keeping up with animations controlled by MAUI.
// To prevent this undesired effect, native animations will be turned off for the duration of the operation.
CATransaction.Begin();
CATransaction.AnimationDuration = 0;

do
{
var backgroundLayer = backgroundLayerEnumerator.Current;
backgroundLayer.Frame = bounds;
}
while (backgroundLayerEnumerator.MoveNext());

CATransaction.Commit();
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,5 @@ Microsoft.Maui.Platform.MauiView.IsMeasureValid(double widthConstraint, double h
*REMOVED*override Microsoft.Maui.Platform.ContentView.SetNeedsLayout() -> void
Microsoft.Maui.Platform.UIEdgeInsetsExtensions
static Microsoft.Maui.Platform.UIEdgeInsetsExtensions.ToThickness(this UIKit.UIEdgeInsets insets) -> Microsoft.Maui.Thickness
override Microsoft.Maui.Handlers.BorderHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect rect) -> void
*REMOVED*override Microsoft.Maui.Handlers.BorderHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect rect) -> void
*REMOVED*override Microsoft.Maui.Platform.MauiLabel.InvalidateIntrinsicContentSize() -> void
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Microsoft.Maui.Platform.MauiScrollView
Microsoft.Maui.Platform.MauiScrollView.MauiScrollView() -> void
Microsoft.Maui.Platform.KeyboardAutoManagerScroll
Microsoft.Maui.SoftInputExtensions
override Microsoft.Maui.Handlers.BorderHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect rect) -> void
*REMOVED*override Microsoft.Maui.Handlers.BorderHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect rect) -> void
override Microsoft.Maui.Handlers.ButtonHandler.NeedsContainer.get -> bool
override Microsoft.Maui.Handlers.ContentViewHandler.DisconnectHandler(Microsoft.Maui.Platform.ContentView! platformView) -> void
override Microsoft.Maui.Handlers.RefreshViewHandler.SetVirtualView(Microsoft.Maui.IView! view) -> void
Expand Down
Loading