SkiaSharp icon indicating copy to clipboard operation
SkiaSharp copied to clipboard

[BUG] SkCanvas PaintSurface Event not triggering on physical iOS 16 device

Open AlexanderSCK opened this issue 2 years ago • 3 comments

Description

I have a SKCanvas control which works fine when deployed to Android 13, and calls the PaintSurface Event, but when deployed using hot reload on a physical iOS 16 device an iphone 8 the Paintsurface event is never triggered therefore, no view is rendered. The control is initiated in a Grid using Net maui. A paid Apple Developer account is used, since that was the cause in earlier reports of the same issue. The framework is .NET 6, the following dependencies are installed : SkiaSharp.Views.Maui.Controls and SkiaSharp.Views.Maui.Core. As I mentioned it is working accordinlgly on Android so the cause is not the code.

Code

public partial class RatingView : SKCanvasView { private PanGestureRecognizer panGestureRecognizer = new PanGestureRecognizer(); private double touchX; private double touchY;

    public RatingView()
    {
        this.BackgroundColor = Colors.Transparent;
        this.PaintSurface += Handle_PaintSurface;
        this.EnableTouchEvents = true;
        this.panGestureRecognizer.PanUpdated += PanGestureRecognizer_PanUpdated;
        this.GestureRecognizers.Add(panGestureRecognizer);

    }

    #region BindableProperties

    public static readonly BindableProperty ValueProperty = BindableProperty.Create(nameof(Value), typeof(double), typeof(RatingView), default(double), propertyChanged: OnValueChanged);
    public static readonly BindableProperty PathProperty = BindableProperty.Create(nameof(Path), typeof(string), typeof(RatingView), PathConstants.AnnoncerStar, propertyChanged: OnPropertyChanged);
    public static readonly BindableProperty CountProperty = BindableProperty.Create(nameof(Count), typeof(int), typeof(RatingView), 5, propertyChanged: OnPropertyChanged);
    public static readonly BindableProperty ColorOnProperty = BindableProperty.Create(nameof(ColorOn), typeof(Color), typeof(RatingView), MaterialColors.Amber.ToMauiColor(), propertyChanged: ColorOnChanged);
    public static readonly BindableProperty OutlineOnColorProperty = BindableProperty.Create(nameof(OutlineOnColor), typeof(Color), typeof(RatingView), SKColors.Transparent.ToMauiColor(), propertyChanged: OutlineOnColorChanged);
    public static readonly BindableProperty OutlineOffColorProperty = BindableProperty.Create(nameof(OutlineOffColor), typeof(Color), typeof(RatingView), MaterialColors.PlateYellow.ToMauiColor(), propertyChanged: OutlineOffColorChanged);
    public static readonly BindableProperty RatingTypeProperty = BindableProperty.Create(nameof(RatingType), typeof(RatingType), typeof(RatingView), RatingType.Floating, propertyChanged: OnPropertyChanged);

    public double Value
    {
        get { return (double)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, this.ClampValue(value)); }
    }

    public double ClampValue(double val)
    {
        if (val < 0)
            return 0;
        else if (val > this.Count)
            return this.Count;
        else
            return val;
    }
    public string Path
    {
        get { return (string)GetValue(PathProperty); }
        set { SetValue(PathProperty, value); }
    }

    public int Count
    {
        get { return (int)GetValue(CountProperty); }
        set { SetValue(CountProperty, value); }
    }

    public Color ColorOn
    {
        get { return (Color)GetValue(ColorOnProperty); }
        set { SetValue(ColorOnProperty, value); }
    }

    public Color OutlineOnColor
    {
        get { return (Color)GetValue(OutlineOnColorProperty); }
        set { SetValue(OutlineOnColorProperty, value); }
    }

    public Color OutlineOffColor
    {
        get { return (Color)GetValue(OutlineOffColorProperty); }
        set { SetValue(OutlineOffColorProperty, value); }
    }

    public RatingType RatingType
    {
        get { return (RatingType)GetValue(RatingTypeProperty); }
        set { SetValue(RatingTypeProperty, value); }
    }

    public float Spacing { get; set; } = 20;

    /// <summary>
    /// Gets or sets the color of the canvas background.
    /// </summary>
    /// <value>The color of the canvas background.</value>
    public SKColor CanvasBackgroundColor { get; set; } = SKColors.Transparent;

    /// <summary>
    /// Gets or sets the width of the stroke.
    /// </summary>
    /// <value>The width of the stroke.</value>
    public float StrokeWidth { get; set; } = 12f;

    private float ItemWidth { get; set; }
    private float ItemHeight { get; set; }
    private float CanvasScale { get; set; }
    private SKColor SKColorOn { get; set; } = MaterialColors.PlateYellow;
    private SKColor SKOutlineOnColor { get; set; } = MaterialColors.Black;
    private SKColor SKOutlineOffColor { get; set; } = MaterialColors.PlateYellow;

    #endregion


    private void Handle_PaintSurface(object sender, SKPaintSurfaceEventArgs e)
    {
        this.Draw(e.Surface.Canvas, e.Info.Width, e.Info.Height);
    }

    public void Draw(SKCanvas canvas, int width, int height)
    {
        canvas.Clear(this.CanvasBackgroundColor);

        var path = SKPath.ParseSvgPathData(this.Path);

        var itemWidth = ((width - (this.Count - 1) * this.Spacing)) / this.Count;
        var scaleX = (itemWidth / (path.Bounds.Width));
        scaleX = (itemWidth - scaleX * this.StrokeWidth) / path.Bounds.Width;

        this.ItemHeight = height;
        var scaleY = this.ItemHeight / (path.Bounds.Height);
        scaleY = (this.ItemHeight - scaleY * this.StrokeWidth) / (path.Bounds.Height);

        this.CanvasScale = Math.Min(scaleX, scaleY);
        this.ItemWidth = path.Bounds.Width * this.CanvasScale;

        canvas.Scale(this.CanvasScale);
        canvas.Translate(this.StrokeWidth / 2, this.StrokeWidth / 2);
        canvas.Translate(-path.Bounds.Left, 0);
        canvas.Translate(0, -path.Bounds.Top);

        using (var strokePaint = new SKPaint
        {
            Style = SKPaintStyle.Stroke,
            Color = this.OutlineOffColor.ToSKColor(),
            StrokeWidth = this.StrokeWidth,
            StrokeCap = SKStrokeCap.Round,
            IsAntialias = true,
        })
        using (var fillPaint = new SKPaint
        {
            Style = SKPaintStyle.Fill,
            Color = this.SKColorOn,
            IsAntialias = true,
        })
        {
            for (int i = 0; i < this.Count; i++)
            {
                if (i <= this.Value - 1) // Full
                {
                    canvas.DrawPath(path, fillPaint);
                    canvas.DrawPath(path, strokePaint);
                }
                else if (i < this.Value) //Partial
                {
                    float filledPercentage = (float)(this.Value - Math.Truncate(this.Value));
                    strokePaint.Color = this.SKOutlineOffColor;
                    canvas.DrawPath(path, strokePaint);

                    using (var rectPath = new SKPath())
                    {
                        var rect = SKRect.Create(path.Bounds.Left + path.Bounds.Width * filledPercentage, path.Bounds.Top, path.Bounds.Width * (1 - filledPercentage), this.ItemHeight * 2);
                        rectPath.AddRect(rect);
                        canvas.ClipPath(rectPath, SKClipOperation.Difference);
                        canvas.DrawPath(path, fillPaint);
                    }
                }
                else //Empty
                {
                    strokePaint.Color = this.SKOutlineOffColor;
                    canvas.DrawPath(path, strokePaint);
                }

                canvas.Translate((this.ItemWidth + this.Spacing) / this.CanvasScale, 0);
            }
        }

    }
    protected override void OnTouch(SKTouchEventArgs e)
    {
        this.touchX = e.Location.X;
        this.touchY = e.Location.Y;
        this.SetValue(touchX, touchY);
        this.InvalidateSurface();
    }

    public void SetValue(double x, double y)
    {
        var val = this.CalculateValue(x);
        switch (this.RatingType)
        {
            case RatingType.Full:
                this.Value = ClampValue((double)Math.Ceiling(val));
                break;
            case RatingType.Half:
                this.Value = ClampValue((double)Math.Round(val * 2) / 2);
                break;
            case RatingType.Floating:
                this.Value = ClampValue(val);
                break;
        }
    }

    private double CalculateValue(double x)
    {
        if (x < this.ItemWidth)
            return (double)x / this.ItemWidth;
        else if (x < this.ItemWidth + this.Spacing)
            return 1;
        else
            return 1 + CalculateValue(x - (this.ItemWidth + this.Spacing));
    }

    private void PanGestureRecognizer_PanUpdated(object sender, PanUpdatedEventArgs e)
    {
        var point = ConvertToPixel(new Point(e.TotalX, e.TotalY));
        if (e.StatusType != GestureStatus.Completed)
        {
            this.SetValue(touchX + point.X, touchY + e.TotalY);
            this.InvalidateSurface();
        }
    }

    private static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var view = bindable as RatingView;
        view.InvalidateSurface();
    }

    private static void OnValueChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var view = bindable as RatingView;
        view.Value = view.ClampValue((double)newValue);
        OnPropertyChanged(bindable, oldValue, newValue);
    }

    private static void ColorOnChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var view = bindable as RatingView;
        view.SKColorOn = ((Color)newValue).ToSKColor();
        OnPropertyChanged(bindable, oldValue, newValue);
    }

    private static void OutlineOffColorChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var view = bindable as RatingView;
        view.SKOutlineOffColor = ((Color)newValue).ToSKColor();
        OnPropertyChanged(bindable, oldValue, newValue);
    }

    private static void OutlineOnColorChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var view = bindable as RatingView;
        view.SKOutlineOnColor = ((Color)newValue).ToSKColor();
        OnPropertyChanged(bindable, oldValue, newValue);
    }

    SKPoint ConvertToPixel(Point pt)
    {
        return new SKPoint((float)(this.CanvasSize.Width * pt.X / this.Width),
                           (float)(this.CanvasSize.Height * pt.Y / this.Height));
    }

}

Expected Behavior

To draw the Canvas view.

Actual Behavior

Basic Information

  • Version with issue: 2.88.3
  • Last known good version:
  • IDE: Visual Studio
  • Platform Target Frameworks:
    • Android:
    • iOS: 16
    • Linux:
    • macOS:
    • Tizen:
    • tvOS:
    • UWP:
    • watchOS:
    • Windows Classic:
  • Target Devices: iPhone 8
Detailed IDE/OS information (click to expand)

PASTE ANY DETAILED VERSION INFO HERE

Screenshots

Reproduction Link

AlexanderSCK avatar Dec 07 '22 16:12 AlexanderSCK

https://github.com/AlexanderSCK/RatingControl - Reproduction repository

AlexanderSCK avatar Dec 07 '22 17:12 AlexanderSCK

I performed tests with the SkiaSharpDemo sample and once again is not triggering the Paint Surface event, it just shows a white page

AlexanderSCK avatar Dec 07 '22 17:12 AlexanderSCK

+1 the same issue. Is there any update? Appreciate help, thanks!

vitalii-smal avatar Nov 30 '23 10:11 vitalii-smal