iOS CustomRenderer never gets a proper size, so it can't draw correctly.

ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

I'm trying to create a custom Tile that will draw a shape that basically is a rectangle with the upper-right corner notched out.
The Android CustomRenderer works fine:
tile_loc.png
This tile shape is just the background shape. The image and text etc. is then laid on top of it by the TileControl that uses this TileShape at the lowest Z-order level.

However the iOS CustomerRenderer only ever produces a rectangle (no notch). They all get the assigned colors, so I figure I'm not too far wrong.
Whenever the Tile renders and the OnDraw is called, the passed in CGRect is always 1x1; not the expected 200x160.
Thus the calculations for drawing never work out.

I have left this alone for weeks, then returned to for days, then let it go again, then returned to it again. In the end I think there's just something basic about custom drawing in iOS that I'm not aware of. I don't know what it is I don't know, and so I don't even know what to Google at this point. I've gone through every walk-thru from the Xamarin site that I can find... scrubbed through all the sample project code that seems relevant. I'm at my whit's end.

If anyone sees something I'm missing, doing wrong... has any kind of example with an iOS renderer that actually draws a shape of some kind... anything I can dissect as an example... I'd really appreciate it.

The iOS renderer:

using System;
using System.Collections.Generic;
using System.Text;
using CoreGraphics;
using Redacted.Controls;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using Redacted.iOS.Controls;

[assembly: ExportRenderer(typeof(TileShape), typeof(TileShapeRenderer))]
namespace Redacted.iOS.Controls
{
    //https://developer.xamarin.com/guides/xamarin-forms/custom-renderer/view/
    //https://github.com/xamarin/xamarin-forms-samples/blob/master/CustomRenderers/View/iOS/CameraPreviewRenderer.cs
    // https://github.com/xamarin/xamarin-forms-samples/blob/master/CustomRenderers/View/iOS/UICameraPreview.cs

    public class TileShapeRenderer : ViewRenderer<TileShape, UIView>
    {
        private TileShapeView thisView;
        protected override void OnElementChanged(ElementChangedEventArgs<TileShape> e)
        {

            base.OnElementChanged(e);

            if (Control == null)
            {
                if (thisView == null) thisView = new TileShapeView();
                if (e.NewElement != null) thisView.tileColor = e.NewElement.TileColor;
                SetNativeControl(thisView);
            }
            if (e.OldElement != null)
            {
                // Unsubscribe
                //uiCameraPreview.Tapped -= OnCameraPreviewTapped;
            }
            if (e.NewElement != null)
            {
                thisView.tileColor = ((TileShape)e.NewElement).TileColor;
            }
        }


    }

    public class TileShapeView : UIView
    {
        public Color tileColor = Color.Fuchsia;//If they are all this color then I know I'm not passing/calculating the color correctly
        public override void Draw(CoreGraphics.CGRect rect)
        {
            base.Draw(rect);
            var actualWidth = rect.Width;
            var actualHeight = rect.Height;

            //No matter what I do, the rect is always 1 by 1
            //Yet when it appears on the device it renders as a complete rectangle of 200x160

            var inset = actualWidth / 2.5;//3 on Android but pixel shape and aspect ratio is different on iOS

            using (var g = UIGraphics.GetCurrentContext())
            {
                g.SetLineWidth(1);
                var color = new UIColor((nfloat)tileColor.R,
                                           (nfloat)tileColor.G,
                                           (nfloat)tileColor.B,
                                           1);
                color.SetStroke();
                color.SetFill();

                #region Triangle 
                ////https://developer.xamarin.com/guides/ios/application_fundamentals/graphics_animation_ios/core_graphics/
                //var path = new CGPath();

                //path.AddLines(new CGPoint[]{
                //new CGPoint (100, 200),
                //new CGPoint (160, 100),
                //new CGPoint (220, 200)});

                //path.CloseSubpath();
                #endregion Triangle




                #region Rect with notch in upper right 
                CGPoint[] pts =
                {
                    new CGPoint(0, 0),
                    new CGPoint(0, actualHeight),
                    new CGPoint(actualWidth, actualHeight),
                    new CGPoint(actualWidth, inset),
                    new CGPoint(actualWidth-inset, 0),
                    new CGPoint(0, 0)
                };
                var path = new CGPath();
                path.AddLines(pts);

                #endregion Rect with notch in upper right



                path.CloseSubpath();

                //add geometry to graphics context and draw it
                g.AddPath(path);
                g.DrawPath(CGPathDrawingMode.FillStroke);
            };
        }

    }
}

Being called from the ContentView control as:

<controls:TileShape Grid.Row="0"
                    Grid.RowSpan="4"
                    Grid.Column="0"
                    Grid.ColumnSpan="4"
                    BackgroundColor="Transparent"
                    TileColor="{Binding TileColor}" />

Answers

  • gdkgdk INMember ✭✭✭

    Hi, In Draw method you can say CGRect is 1 * 1 but you want 200 * 160 right?, I think that method default CGRect value is 1*1. You can set CGRect value manually what you want.

         var yourRect = new CGRect(0,0,200,160);
    

    Then you can use this CGRect values where you want, you can change these two values than your rectangle size will be changed.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Thanks @gdk - I'll give that a try.
    The problem with hard coding the values in OnDraw is that the control no longer becomes dynamic from the XAML in the PCL. What if I change the size of the tile in the PCL? What if the tile should automatically set its size based on the available space of the container its in? For now as a test I'll try hard setting the size of the Rect and post back the result.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    @ClintStLaurent said:
    Thanks @gdk - I'll give that a try.
    For now as a test I'll try hard setting the size of the Rect and post back the result.

    No joy. The rect accepts the new size, does all the right math. Yet its a complete rectangle that is rendered on the device; not the notched rectangle as seen in the droid rendered image.

    I thought maybe the actual size was somehow smaller than the requested size, so as a test I dropped the hardcoded size in half to 80x100. I figured the worst case should have been a small tile in the middle of the allocated space. Again, no change; still got a full rectangle taking the full space.

    Then I tried doubling the intended size to 320x400. Still no change.

    The more I experiment with the various drawing options, fill types etc., the more the behavior confounds me.
    For example,
    Hard coding the size to 80x100 on a control given 160x200 space I would expect a rendered tile shape to just be 1/4 of the overall space. Its not: It still renders as big as the 160x200 control real estate.
    • with a fill of transparent color, and a stroke color of black, and DrawPath style of just .Stroke one would expect the tile to have no fill and an outlined path. In reality it created a completely filled black rectangle.
    • with a fill of red and a stroke of black and Drawpath style of .EOFillStoke, one would expect a red tile with a black outline stroke. Instead I get an all black tile. how do you get an all black drawing when you specify a red fill?

    I guess I'm going to have to make a new solution and just play with this on the "Welcome to Xamarin" default instead of in the confines of my complex application that is in development.

            public override void Draw(CoreGraphics.CGRect rect)
            {
                //No matter what I do, the rect is always 1 by 1
                //Yet when it appears on the device it renders as a complete rectangle of full size
    
                rect.Width = 80;
                rect.Height = 100;
    
                base.Draw(rect);
                var actualWidth = rect.Width;
                var actualHeight = rect.Height;
    
    
                var inset = actualWidth / 2.5;//3 on Android but pixel shape and aspect ratio is different on iOS
    
                using (var g = UIGraphics.GetCurrentContext())
                {
                base.Draw(rect);
                    g.SetLineWidth(2);
                    //var color = new UIColor((nfloat)tileColor.R,
                    //                           (nfloat)tileColor.G,
                    //                           (nfloat)tileColor.B,
                    //                           1);
                    //color.SetStroke();
                    UIColor.Black.SetStroke();
                    UIColor.Red.SetFill();
                    //var trans = new UIColor(0,0,0,0);
                    //trans.SetFill();
    
                    #region Rect with notch in upper right 
                    CGPoint[] pts =
                    {
                        new CGPoint(0, 0),
                        new CGPoint(0, actualHeight),
                        new CGPoint(actualWidth, actualHeight),
                        new CGPoint(actualWidth, inset),
                        new CGPoint(actualWidth-inset, 0),
                        new CGPoint(0, 0)
                    };
                    var path = new CGPath();
                    path.AddLines(pts);
    
                    #endregion Rect with notch in upper right
    
                    path.CloseSubpath();
    
                    //add geometry to graphics context and draw it
                    g.AddPath(path);
                    g.DrawPath(CGPathDrawingMode.EOFillStroke);
                };
            }
    
Sign In or Register to comment.