xamarin / Xamarin.Forms

Xamarin.Forms is no longer supported. Migrate your apps to .NET MAUI.
https://aka.ms/xamarin-upgrade
Other
5.62k stars 1.87k forks source link

[Bug] System.NotSupportedException: 'Undable to activate instance of type SkiaSharp.Views.Forms.SKCanvasViewRenderer from native handle' #11757

Open ibocon opened 4 years ago

ibocon commented 4 years ago

Description

I am trying to replace ContnetView.Contnet by touch.

Steps to Reproduce

  1. Create blank Xamarin project.
  2. Add 'MyContetView.cs' file.
  3. copy & paste source code

    public class MyContentView : ContentView
    {
        private string _text;
        private SKCanvasView _canvasView;
        private Editor _editorView;
    
        public MyContentView()
        {
            _text = "Hello, World!";
    
            _canvasView = new SKCanvasView()
            {
                WidthRequest = 100,
                HeightRequest = 100,
                EnableTouchEvents = true
            };
            _canvasView.PaintSurface += (sender, e) =>
            {
                e.Surface.Canvas.Clear();
    
                var rect = new SKRect(0, 0, 
                    (float)(Width * Xamarin.Essentials.DeviceDisplay.MainDisplayInfo.Density), 
                    (float)(Height * Xamarin.Essentials.DeviceDisplay.MainDisplayInfo.Density));
                var rectPaint = new SKPaint()
                {
                    Color = SKColors.Red,
                    IsStroke = false,
                    StrokeWidth = 1
                };
                e.Surface.Canvas.DrawRect(rect, rectPaint);
    
                var textPaint = new SKPaint()
                {
                    Color = SKColors.Black,
                    IsAntialias = true,
                    TextSize = 16
                };
                e.Surface.Canvas.DrawText(_text, 0, 0, textPaint);
            };
            _canvasView.Touch += (sender, e) =>
            {
                ToggleContent();
                e.Handled = true;
            };
    
            _editorView = new Editor()
            {
                AutoSize = EditorAutoSizeOption.TextChanges
            };
    
            _editorView.Completed += (sender, e) =>
            {
                _text = _editorView.Text;
            };
            _editorView.Unfocused += (sender, e) =>
            {
                ToggleContent();
            };
    
            _canvasView.InvalidateSurface();
            Content = _canvasView;
        }
    
        public void ToggleContent()
        {
            if (Content == _canvasView)
            {
                Content = _editorView;
            }
            else
            {
                _canvasView.InvalidateSurface();
                Content = _canvasView;
            }
        }
    }

Expected Behavior

MyContnetView.Content changed.

Actual Behavior

System.NotSupportedException

Basic Information

Reproduction Link

Reproduce Project

StackOverflow Link

System.NotSupportedException: 'Undable to activate instance of type SkiaSharp.Views.Forms.SKCanvasViewRenderer from native handle'

rmarinho commented 4 years ago

cc @mattleibow

mattleibow commented 4 years ago

@rmarinho I am not sure why this would happen. Is this a result of something that needs to be done in a renderer?

This is my renderer:

    public class SKCanvasViewRenderer : SKCanvasViewRendererBase<SKFormsView, SKNativeView>
    {
        public SKCanvasViewRenderer(Context context)
            : base(context)
        {
        }

        [EditorBrowsable (EditorBrowsableState.Never)]
        [Obsolete("This constructor is obsolete as of version 2.5. Please use SKCanvasViewRenderer(Context) instead.")]
        public SKCanvasViewRenderer()
            : base()
        {
        }

        protected override SKNativeView CreateNativeControl() =>
            GetType() == typeof(SKCanvasViewRenderer)
                ? new SKNativeView(Context)
                : base.CreateNativeControl();
    }

The canvas appears on the screen at startup, but when the touch event fires and swaps out the view, Forms disposes it. Then for some reason it starts up again?

A big stack trace: https://gist.github.com/mattleibow/61939af789e7a2133be41d29c846729e

ibocon commented 4 years ago

Workaround

Instead of replacing ContentView.Content, toggle VisualElement.IsVisible between SKCanvasView and Editor.

<ContentView.Content>
    <AbsoluteLayout>
        <skia:SKCanvasView x:Name="CanvasView"
                                          AbsoluteLayout.LayoutFlags="All"
                                          AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
                                          IsVisible="True" 
                                          EnableTouchEvents="True"/>
        <Editor x:Name="EditorView"
                     AbsoluteLayout.LayoutFlags="All"
                     AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
                     IsVisible="False"
                     AutoSize="TextChanges" />
    </AbsoluteLayout>
</ContentView.Content>
public void ToggleState()
{
            if (isCanvasView)
            {
                CanvasView.IsVisible = true;
                CanvasView.Focus();

                EditorView.IsVisible = false;
                EditorView.Unfocus();
            }
            else
            {
                CanvasView.IsVisible = false;
                CanvasView.Unfocus();

                EditorView.IsVisible = true;
                EditorView.Focus();
            }
}

For cleaner code, State pattern could be a great solution than if.

mattleibow commented 4 years ago

Great that you have a workaround. If the forms team notices a bug, then I'll fix it. Hopefully we can get this going nicely soon.

charlesroddie commented 3 years ago

Also encountered this issue and had to work around. Some interaction with an Android renderer and touch. With minor tweaks the error would sometimes appear as System.ObjectDisposedException: Cannot access a disposed object. Object name: '...Renderer' We use renderers to handle Keyboard input. The workaround ended up OK so we are not depending on a fix for this. Maybe with MAUI handlers having less magic than renderers there will be less chance of running into this in future.

peter-chapman commented 3 years ago

I am also seeing this error when removing a SKCanvasView from Form. The trigger for this exception seems to be a second touch event coming in that has nowhere to go as the .net SKCanvasView has already been disposed. Presumably there is some unfortunate logic that means that when you remove the SKCanvasView in the middle of a touch callback the dispose doesn't push down correctly. You can set the SKCanvasView to invisible and then use Device.BeginInvokeOnMainThread to schedule a later removal.