[MacOS] Move ImageRenderer to use NSImageView (#5600)
authorRui Marinho <me@ruimarinho.net>
Tue, 19 Mar 2019 18:10:10 +0000 (18:10 +0000)
committerGitHub <noreply@github.com>
Tue, 19 Mar 2019 18:10:10 +0000 (18:10 +0000)
* IMAGE https://github.com/xamarin/Xamarin.Forms/issues/5204

* Updated "no image source" behavior - return base value

* Added test case

* Updated test case number

* fixed file name

* [MacOS] Move ImageRenderer to use NSImageView

* [Macos,iOS] Fix sharing coding

Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5204.cs [new file with mode: 0644]
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
Xamarin.Forms.Platform.MacOS/Controls/FormsImageView.cs
Xamarin.Forms.Platform.MacOS/Extensions/ImageExtensions.cs [new file with mode: 0644]
Xamarin.Forms.Platform.MacOS/Renderers/ImageRenderer.cs
Xamarin.Forms.Platform.MacOS/Xamarin.Forms.Platform.macOS.csproj
Xamarin.Forms.Platform.iOS/Renderers/IImageVisualElementRenderer.cs
Xamarin.Forms.Platform.iOS/Renderers/ImageElementManager.cs
Xamarin.Forms.Platform.iOS/Renderers/ImageRenderer.cs

diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5204.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5204.cs
new file mode 100644 (file)
index 0000000..234d347
--- /dev/null
@@ -0,0 +1,32 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls.Issues
+{
+       [Preserve(AllMembers = true)]
+       [Issue(IssueTracker.Github, 5204, "[MacOS] Image size issue (not rendered if skip setting WidthRequest and HeightRequest", PlatformAffected.macOS)]
+       public class Issue5204 : TestContentPage
+       {
+               protected override void Init()
+               {
+                       Title = "You should see image";
+                       Content = new StackLayout
+                       {
+                               VerticalOptions = LayoutOptions.CenterAndExpand,
+                               HorizontalOptions = LayoutOptions.CenterAndExpand,
+                               Children = {
+                                       new Image
+                                       {
+                                               BackgroundColor = Color.Black,
+                                               Source = "https://user-images.githubusercontent.com/10124814/53306353-27302b80-389d-11e9-98ce-690db32f1ee3.jpg"
+                                       }
+                               }
+                       };
+               }
+       }
+}
\ No newline at end of file
index 667b86a..652f814 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)Issue3318.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue4493.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue5172.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Issue5204.cs" />
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="$(MSBuildThisFileDirectory)Bugzilla22229.xaml">
index 6d7cd3c..20d2560 100644 (file)
@@ -1,19 +1,14 @@
 using System;
 using AppKit;
 using CoreAnimation;
+using CoreGraphics;
 
 namespace Xamarin.Forms.Platform.MacOS
 {
-       internal class FormsNSImageView : NSView
+       internal class FormsNSImageView : NSImageView
        {
                bool _isOpaque;
 
-               public FormsNSImageView()
-               {
-                       Layer = new CALayer();
-                       WantsLayer = true;
-               }
-
                public void SetIsOpaque(bool isOpaque)
                {
                        _isOpaque = isOpaque;
diff --git a/Xamarin.Forms.Platform.MacOS/Extensions/ImageExtensions.cs b/Xamarin.Forms.Platform.MacOS/Extensions/ImageExtensions.cs
new file mode 100644 (file)
index 0000000..cadda0c
--- /dev/null
@@ -0,0 +1,23 @@
+using System;
+using CoreAnimation;
+using Foundation;
+
+namespace Xamarin.Forms.Platform.MacOS
+{
+       public static class ImageExtensions
+       {
+               public static NSString ToNSViewContentMode(this Aspect aspect)
+               {
+                       switch (aspect)
+                       {
+                               case Aspect.AspectFill:
+                                       return CALayer.GravityResizeAspectFill;
+                               case Aspect.Fill:
+                                       return CALayer.GravityResize;
+                               case Aspect.AspectFit:
+                               default:
+                                       return CALayer.GravityResizeAspect;
+                       }
+               }
+       }
+}
index 8450cf4..8b22895 100644 (file)
@@ -1,15 +1,19 @@
 using System;
 using System.ComponentModel;
+using System.Threading.Tasks;
 using AppKit;
-using CoreAnimation;
-using CoreGraphics;
 
 namespace Xamarin.Forms.Platform.MacOS
 {
-       public class ImageRenderer : ViewRenderer<Image, NSView>
+       public class ImageRenderer : ViewRenderer<Image, NSImageView>, IImageVisualElementRenderer
        {
                bool _isDisposed;
 
+               public ImageRenderer()
+               {
+                       ImageElementManager.Init(this);
+               }
+
                protected override void Dispose(bool disposing)
                {
                        if (_isDisposed)
@@ -17,9 +21,10 @@ namespace Xamarin.Forms.Platform.MacOS
 
                        if (disposing)
                        {
-                               CGImage oldUIImage;
-                               if (Control != null && (oldUIImage = Control.Layer.Contents) != null)
+                               NSImage oldUIImage;
+                               if (Control != null && (oldUIImage = Control.Image) != null)
                                {
+                                       ImageElementManager.Dispose(this);
                                        oldUIImage.Dispose();
                                }
                        }
@@ -29,7 +34,7 @@ namespace Xamarin.Forms.Platform.MacOS
                        base.Dispose(disposing);
                }
 
-               protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
+               protected override async void OnElementChanged(ElementChangedEventArgs<Image> e)
                {
                        if (e.NewElement != null)
                        {
@@ -38,94 +43,50 @@ namespace Xamarin.Forms.Platform.MacOS
                                        var imageView = new FormsNSImageView();
                                        SetNativeControl(imageView);
                                }
-                               SetAspect();
-                               SetImage(e.OldElement);
-                               SetOpacity();
+
+                               await TrySetImage(e.OldElement as Image);
                        }
 
                        base.OnElementChanged(e);
                }
 
-               protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+               protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
                {
                        base.OnElementPropertyChanged(sender, e);
+
                        if (e.PropertyName == Image.SourceProperty.PropertyName)
-                               SetImage();
-                       else if (e.PropertyName == Image.IsOpaqueProperty.PropertyName)
-                               SetOpacity();
-                       else if (e.PropertyName == Image.AspectProperty.PropertyName)
-                               SetAspect();
+                               await TrySetImage().ConfigureAwait(false);
                }
 
-               void SetAspect()
+               protected virtual async Task TrySetImage(Image previous = null)
                {
-                       switch (Element.Aspect)
+                       // By default we'll just catch and log any exceptions thrown by SetImage so they don't bring down
+                       // the application; a custom renderer can override this method and handle exceptions from
+                       // SetImage differently if it wants to
+
+                       try
                        {
-                               case Aspect.AspectFill:
-                                       Control.Layer.ContentsGravity = CALayer.GravityResizeAspectFill;
-                                       break;
-                               case Aspect.Fill:
-                                       Control.Layer.ContentsGravity = CALayer.GravityResize;
-                                       break;
-                               case Aspect.AspectFit:
-                               default:
-                                       Control.Layer.ContentsGravity = CALayer.GravityResizeAspect;
-                                       break;
+                               await SetImage(previous).ConfigureAwait(false);
                        }
-               }
-
-               async void SetImage(Image oldElement = null)
-               {
-                       var source = Element.Source;
-
-                       if (oldElement != null)
+                       catch (Exception ex)
                        {
-                               var oldSource = oldElement.Source;
-                               if (Equals(oldSource, source))
-                                       return;
-
-                               var imageSource = oldSource as FileImageSource;
-                               if (imageSource != null && source is FileImageSource && imageSource.File == ((FileImageSource)source).File)
-                                       return;
-
-                               Control.Layer.Contents = null;
+                               Internals.Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
                        }
-
-                       IImageSourceHandler handler;
-
-                       Element.SetIsLoading(true);
-
-                       if (source != null && (handler = Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
+                       finally
                        {
-                               NSImage nsImage;
-                               try
-                               {
-                                       nsImage = await handler.LoadImageAsync(source, scale: (float)NSScreen.MainScreen.BackingScaleFactor);
-                               }
-                               catch (OperationCanceledException)
-                               {
-                                       nsImage = null;
-                               }
-
-                               var imageView = Control;
-                               if (imageView != null)
-                                       imageView.Layer.Contents = nsImage != null ? nsImage.CGImage : null;
-                               if (nsImage != null)
-                                       nsImage.Dispose();
-
-                               if (!_isDisposed)
-                                       ((IVisualElementController)Element).NativeSizeChanged();
+                               ((IImageController)Element)?.SetIsLoading(false);
                        }
-                       else
-                               Control.Layer.Contents = null;
-
-                       if (!_isDisposed)
-                               Element.SetIsLoading(false);
                }
 
-               void SetOpacity()
+               protected async Task SetImage(Image oldElement = null)
                {
-                       (Control as FormsNSImageView)?.SetIsOpaque(Element.IsOpaque);
+                       await ImageElementManager.SetImage(this, Element, oldElement).ConfigureAwait(false);
                }
+
+               void IImageVisualElementRenderer.SetImage(NSImage image) => Control.Image = image;
+
+               bool IImageVisualElementRenderer.IsDisposed => _isDisposed;
+
+               NSImageView IImageVisualElementRenderer.GetImage() => Control;
        }
 }
\ No newline at end of file
index 15dd3ce..6f45df7 100644 (file)
     <Compile Include="..\Xamarin.Forms.Platform.iOS\DisposeHelpers.cs">
       <Link>DisposeHelpers.cs</Link>
     </Compile>
+    <Compile Include="..\Xamarin.Forms.Platform.iOS\Renderers\ImageElementManager.cs">
+      <Link>Renderers\ImageElementManager.cs</Link>
+    </Compile>
+    <Compile Include="..\Xamarin.Forms.Platform.iOS\Renderers\IImageVisualElementRenderer.cs">
+      <Link>Renderers\IImageVisualElementRenderer.cs</Link>
+    </Compile>
+    <Compile Include="Extensions\ImageExtensions.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Xamarin.Forms.Platform\Xamarin.Forms.Platform.csproj">
index 59f9b24..b8e9153 100644 (file)
@@ -1,11 +1,17 @@
-using UIKit;
-
+#if __MOBILE__
+using NativeImageView = UIKit.UIImageView;
+using NativeImage = UIKit.UIImage;
 namespace Xamarin.Forms.Platform.iOS
+#else
+using NativeImageView = AppKit.NSImageView;
+using NativeImage = AppKit.NSImage;
+namespace Xamarin.Forms.Platform.MacOS
+#endif
 {
        public interface IImageVisualElementRenderer : IVisualNativeElementRenderer
        {
-               void SetImage(UIImage image);
+               void SetImage(NativeImage image);
                bool IsDisposed { get; }
-               UIImageView GetImage();
+               NativeImageView GetImage();
        }
 }
\ No newline at end of file
index 70283fc..38ba61f 100644 (file)
@@ -1,14 +1,18 @@
 using System;
-using System.Collections.Generic;
 using System.ComponentModel;
-using System.Linq;
-using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
-using Foundation;
-using UIKit;
 
+#if __MOBILE__
+using UIKit;
+using NativeImage = UIKit.UIImage;
 namespace Xamarin.Forms.Platform.iOS
+#else
+using AppKit;
+using CoreAnimation;
+using NativeImage = AppKit.NSImage;
+namespace Xamarin.Forms.Platform.MacOS
+#endif
 {
        public static class ImageElementManager
        {
@@ -70,8 +74,11 @@ namespace Xamarin.Forms.Platform.iOS
                        {
                                return;
                        }
-
+#if __MOBILE__
                        Control.ContentMode = imageElement.Aspect.ToUIViewContentMode();
+#else
+                       Control.Layer.ContentsGravity = imageElement.Aspect.ToNSViewContentMode();
+#endif
                }
 
                public static void SetOpacity(IImageVisualElementRenderer renderer, IImageElement imageElement)
@@ -83,8 +90,11 @@ namespace Xamarin.Forms.Platform.iOS
                        {
                                return;
                        }
-
+#if __MOBILE__
                        Control.Opaque = imageElement.IsOpaque;
+#else
+                       (Control as FormsNSImageView)?.SetIsOpaque(imageElement.IsOpaque);
+#endif
                }
 
                public static async Task SetImage(IImageVisualElementRenderer renderer, IImageElement imageElement, Image oldElement = null)
@@ -133,14 +143,19 @@ namespace Xamarin.Forms.Platform.iOS
                        (imageElement as IViewController)?.NativeSizeChanged();
                }
 
-               internal static async Task<UIImage> GetNativeImageAsync(this ImageSource source, CancellationToken cancellationToken = default(CancellationToken))
+               internal static async Task<NativeImage> GetNativeImageAsync(this ImageSource source, CancellationToken cancellationToken = default(CancellationToken))
                {
                        IImageSourceHandler handler;
                        if (source != null && (handler = Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
                        {
                                try
                                {
-                                       return await handler.LoadImageAsync(source, scale: (float)UIScreen.MainScreen.Scale, cancelationToken: cancellationToken);
+#if __MOBILE__
+                                       float scale = (float)UIScreen.MainScreen.Scale;
+#else
+                                       float scale = (float)NSScreen.MainScreen.BackingScaleFactor;
+#endif
+                                       return await handler.LoadImageAsync(source, scale: scale, cancelationToken: cancellationToken);
                                }
                                catch (OperationCanceledException ex)
                                {
index d337b85..46873f5 100644 (file)
@@ -8,7 +8,6 @@ using Foundation;
 using UIKit;
 using Xamarin.Forms.Internals;
 using RectangleF = CoreGraphics.CGRect;
-using System.Linq;
 
 namespace Xamarin.Forms.Platform.iOS
 {