Search code examples
ios.netuikitmaui

Bug in .NET MAUI Custom ScrollView for iOS


I'm currently trying to create a custom ScrollViewer that has a zoom and pan scroll functionality implementing UIKit's ScrollView but there's a bug that whenever the screen's zoomed and the app resumes from a sleep state, the view changes its position, shrinks its ScrollHeight, and zooming out positions the grid on the upper left of the screen. (Refer to the screenshot below.)

It could be a bug in .NET Maui itself but I'm not sure. (on Xamarin.Forms after resuming from sleep the grid zooms out automatically without changing the view's position.)

Is there a work-around or a way to resolve it?

Update: Pressing the button inside the grid while on zoomed state also produces the same bug.

View after zooming out:

Here's the code below to reproduce the problem:

MainPage.xaml

<?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"
             xmlns:controls="clr-namespace:MauiAppTest"
             x:Class="MauiAppTest.MainPage">

    <controls:ZoomableScrollView>
        <Grid Background="pink">
            <VerticalStackLayout
            Padding="30,0"
            Spacing="25">
                <Image
                Source="dotnet_bot.png"
                HeightRequest="185"
                Aspect="AspectFit"
                SemanticProperties.Description="dot net bot in a race car number eight" />

                <Label
                Text="Hello, World!"
                Style="{StaticResource Headline}"
                SemanticProperties.HeadingLevel="Level1" />

                <Label
                Text="Welcome to &#10;.NET Multi-platform App UI"
                Style="{StaticResource SubHeadline}"
                SemanticProperties.HeadingLevel="Level2"
                SemanticProperties.Description="Welcome to dot net Multi platform App U I" />

                <Button
                x:Name="CounterBtn"
                Text="Click me" 
                SemanticProperties.Hint="Counts the number of times you click"
                Clicked="OnCounterClicked"
                HorizontalOptions="Fill" />
            </VerticalStackLayout>
        </Grid>
    </controls:ZoomableScrollView>
</ContentPage>

ZoomableScrollView.cs (Custom ScrollView)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MauiAppTest
{
    public class ZoomableScrollView : ScrollView
    {

    }
}

ZoomableScrollViewHandler.cs

using Microsoft.Maui.Handlers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UIKit;

namespace MauiAppTest.Platforms.iOS
{
    public class ZoomableScrollViewHandler : ScrollViewHandler
    {
        protected override void ConnectHandler(UIScrollView platformView)
        {
            base.ConnectHandler(platformView);
            if(platformView != null)
            {
                platformView.MinimumZoomScale = 1;
                platformView.MaximumZoomScale = 3;
                platformView.ViewForZoomingInScrollView += (UIScrollView sv) =>
                {
                    return platformView.Subviews[0];
                };
            }
        }
        protected override void DisconnectHandler(UIScrollView platformView)
        {
            base.DisconnectHandler(platformView);
        }
    }
}

MauiProgram.cs

using Microsoft.Extensions.Logging;
#if IOS
using  MauiAppTest.Platforms.iOS;
#endif

namespace MauiAppTest
{
    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                    fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
                });
#if IOS
builder.ConfigureMauiHandlers(handlers => 
handlers.AddHandler(typeof(ZoomableScrollView),typeof(ZoomableScrollViewHandler)));
#endif
#if DEBUG
            builder.Logging.AddDebug();
#endif

            return builder.Build();
        }
    }
}

I expected that the ScrollViewer would work without problem as it worked on Xamarin.Forms.

Things I've tried including:

・Using a ScrollViewRenderer(with the use of maui.compatibility)

・Tried getting the ZoomScale and ContentOffset during OnSleep, then setting them again during OnResume.

UPDATE: Here's the code in Xamarin.Forms

ZoomableScrollView.cs (Custom ScrollView)

using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;

namespace TestXamarin
{
    public class ZoomableScrollView : ScrollView
    {
    }
}

ZoomableScrollViewRenderer.cs

using Foundation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TestXamarin;
using TestXamarin.iOS;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(ZoomableScrollView), typeof(ZoomableScrollViewRenderer))]
namespace TestXamarin.iOS
{
    public class ZoomableScrollViewRenderer : ScrollViewRenderer
    {
        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);

            var test = this.Element as ZoomableScrollView;
             
            this.MinimumZoomScale = 1f;
            this.MaximumZoomScale = 3f;
            this.ViewForZoomingInScrollView += (UIScrollView sv) => { return this.Subviews[0]; };
        }
    }
}

works on both Xamarin.Forms 4.8 and 5.0 (haven't tested on lower versions)


Solution

  • Unfortunately, I wasn't able to figure out a workaround to get the Zoom function to work properly on a ScrollViewHandler. Instead, I reused XF's ScrollViewRenderer, and manually set the Frame of the ViewForZooming as a workaround. this eliminates the bug where the screen repositions itself off the screen after sleep or button tap.

                var frameSize = new CGSize(this.Width, this.Height);
                var nativeView = ((UIKit.UIView)zoomableSV.Handler.PlatformView);
                nativeView.Subviews[0].Frame = new CGRect(x: 0, y: 0, width: frameSize.Width , height: frameSize.Height);