Forum Xamarin.Forms

Problem with custom renderer height (iOS)

Hi all,

I have a custom renderer to add an icon to the right and/or left side of a date picker and to allow to draw a border on any side of the date picker.
This works fine as long as the icon I add is defined smaller than the size of the native controls frame.
If the icon is bigger than it seems the new size of the left/right view is not conciderered and the frame size stays the same.
I was able to fix that at least in drawing as the control is now drawn big enough.

However for some reason it seems the new size is not respected by Forms. For example if I have my custom date picker in a Stacklayouts and a button under it, then the button is very close to the date picker but there should be spacing. If I rotate from landscape to portrait, then the button even "slides" into the date picker.

My code for the custom iOS renderer looks like this:

public class CustomDatePickerIOSRenderer : DatePickerRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
        {
            base.OnElementChanged(e);
            if(Element is CustomDatePicker customPicker)
            {
                UpdateLeftIcon(customPicker);
                UpdateRightIcon(customPicker);
            }
        }

    public override void LayoutSubviews()
        {
            base.LayoutSubviews();
            if (Element is CustomDatePicker customPicker)
            {
                ApplyBorder(customPicker);
            }
        }

    protected virtual void ApplyBorder(CustomDatePicker customPicker)
        {
            Control.BorderStyle = UITextBorderStyle.None;
            RemoveBorders();
            nfloat height = Control.Frame.Size.Height;
            if (leftView != null && leftView.Frame.Size.Height > height)
                height = leftView.Frame.Size.Height;
            if (rightView != null && rightView.Frame.Size.Height > height)
                height = rightView.Frame.Size.Height;
            Control.Frame = new CGRect(Control.Frame.X, Control.Frame.Y, Control.Frame.Width, height);
            CGColor color = Element.IsFocused ? customPicker.BorderFocusedColor.ToCGColor() : customPicker.BorderColor.ToCGColor();
            if (customPicker.Border.Left > 0)
                CreateBorderSide(0, 0, customPicker.Border.Left, Control.Frame.Size.Height,color);
            if(customPicker.Border.Right > 0)
                CreateBorderSide(Control.Frame.Size.Width-customPicker.Border.Right,0, customPicker.Border.Right, Control.Frame.Size.Height, color);
            if(customPicker.Border.Top > 0)
                CreateBorderSide(0, 0, Control.Frame.Size.Width,customPicker.Border.Top, color);
            if(customPicker.Border.Bottom > 0)
                CreateBorderSide(0, Control.Frame.Size.Height-customPicker.Border.Bottom, Control.Frame.Size.Width, customPicker.Border.Bottom, color);
        }

        protected virtual void RemoveBorders()
        {
            //remove all previously added borders again (if any)
            foreach (var layer in Control.Layer.Sublayers)
            {
                if (layer.Name != null && layer.Name.Contains("customBorder"))
                    layer.RemoveFromSuperLayer();
            }
        }

        protected virtual void CreateBorderSide(double x,double y,double width,double height,CGColor color)
        {
            CALayer borderLayer = new CALayer();
            borderLayer.Frame = new CGRect(x, y, width, height);
            borderLayer.BackgroundColor = color;
            borderLayer.Name = "customBorder";
            Control.Layer.AddSublayer(borderLayer);
        }

    private UIView leftView, rightView;

        protected virtual async void UpdateLeftIcon(CustomDatePicker customPicker)
        {
            if(customPicker.LeftIcon == null)
            {
                Control.LeftViewMode = UIKit.UITextFieldViewMode.Never;
                Control.LeftView = null;
                leftView = null;
            }
            else
            {
                leftView = await CreateSideView(customPicker.LeftIcon, customPicker.LeftIconSize);
                Control.LeftView = leftView;
                Control.LeftViewMode = UIKit.UITextFieldViewMode.Always;
            }
        }

        protected virtual async void UpdateRightIcon(CustomDatePicker customPicker)
        {
            if (customPicker.RightIcon == null)
            {
                Control.RightViewMode = UIKit.UITextFieldViewMode.Never;
                Control.RightView = null;
                rightView = null;
            }
            else
            {
                rightView = await CreateSideView(customPicker.RightIcon, customPicker.RightIconSize, false);
                Control.RightView = rightView;
                Control.RightViewMode = UIKit.UITextFieldViewMode.Always;
            }
        }

    protected virtual async Task<UIView> CreateSideView(ImageSource icon, int size, bool isLeft = true)
        {
            UIImage leftIconImage = await IosImageHelper.GetUIImageFromImageSourceAsync(icon);
            CGSize iconSize = leftIconImage.Size;
            if (size > -1)
                iconSize = new CGSize((float)size, (float)size);
            UIView paddingView = new UIView(new CGRect(0, 0, iconSize.Width + 8, iconSize.Height+8));
            UIImageView sideView = new UIImageView(new CGRect(isLeft?8:0, 4, iconSize.Width, iconSize.Height));
            sideView.Image = leftIconImage;
            paddingView.AddSubview(sideView);
            return paddingView;
        }
}

So for me it highly seems like setting Control.LeftView or Control.RightView does not affect the actual frame size of the UIControl + it does not update the Xamarin Forms view bounds.
That's why I added the manual "calculation" of the border in ApplyBorder, but as it changes the frame of the iOS native control correctly, it seems to not update the information in the Xamarin Forms Control?
I guess the solution is very simple, somehow to notify the Xamarin Forms Control to remeasure or something but I just can't figure it out right now.

Here are screenshots showing what I mean by not keeping the distance and sliding into the control.

Landscape:

Portrait:

Answers

  • LucasZhangLucasZhang Member, Xamarin Team Xamurai

    You can scale the image with the size you want before you set the icon

    /*
       sourceImage:the iamge that you download
       newSize the size you want ( for example 25*25)
    */
    public UIImage ScalingImageToSize(UIImage sourceImage,CGSize newSize)
    {
    
      if(UIScreen.MainScreen.Scale==2.0) //@2x iPhone 6 7 8 
        {
          UIGraphics.BeginImageContextWithOptions(newSize, false, 2.0f);
        }
    
    
      else if(UIScreen.MainScreen.Scale == 3.0) //@3x iPhone 6p 7p 8p...
        {
          UIGraphics.BeginImageContextWithOptions(newSize, false, 3.0f);
        }
    
      else
        {
         UIGraphics.BeginImageContext(newSize);
        }
    
       sourceImage.Draw(new CGRect(0, 0, newSize.Width, newSize.Height));
    
       UIImage newImage = UIGraphics.GetImageFromCurrentImageContext();
    
       UIGraphics.EndImageContext();
    
       return newImage;
    
      } 
    
  • GraverobberGraverobber Member ✭✭✭

    Thank you @LucasZhang for responding.

    Will pre-scaling fix the issue of the wrongly measured Entry?
    Right now I give a Left/Right view with the dimensions of the desired size and add the image view to this side view.
    The scaling of the image then is fine it is just that the Entry is not aware of the dimensions of the sideview.
    Will It know its size if the image is pre-scaled?

  • LucasZhangLucasZhang Member, Xamarin Team Xamurai

    You don't need to measure the frame of entry any more if you pre-scale the image .

Sign In or Register to comment.