Skip to content

Commit

Permalink
Fixes #18204 border lagging behind content on iOS and improves initia…
Browse files Browse the repository at this point in the history
…l render performance
  • Loading branch information
albyrock87 committed Jun 24, 2024
1 parent 4fabb3e commit 5b40543
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 24 deletions.
17 changes: 17 additions & 0 deletions src/Controls/tests/TestCases/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/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 @@ -160,5 +160,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.ScrollViewHandler.NeedsContainer.get -> bool
Expand Down

0 comments on commit 5b40543

Please sign in to comment.