Generic custom renderers?

bitsprintbitsprint GBUniversity

Hi,

I started a project pre Forms but started to convert it over as soon as Forms was available.

In my old iOS project I created a base Button class to set the font and the dimensions since I want my buttons to have a consistent style. Then I created separate button classes like Ok Button, so I can set the background image for different button 'types'.

As I was writing the custom ButtonRenderers it occurred to me that I'm really doing the same stuff for each button, each button 'type' just sets a different background image so I just need to cast to a different button type in the renderer. So I tried making a single generic CustomButtonRenderer, here it is with an example of one of the buttons and the base button:

[assembly: Xamarin.Forms.ExportRenderer (typeof (MyApp.ActionButton), typeof (MyApp.iOS.CustomButtonRenderer))]
[assembly: Xamarin.Forms.ExportRenderer (typeof (MyApp.ConfirmButton), typeof (MyApp.iOS.CustomButtonRenderer))]
[assembly: Xamarin.Forms.ExportRenderer (typeof (MyApp.EmailButton), typeof (MyApp.iOS.CustomButtonRenderer))]
[assembly: Xamarin.Forms.ExportRenderer (typeof (MyApp.FacebookButton), typeof (MyApp.iOS.CustomButtonRenderer))]
[assembly: Xamarin.Forms.ExportRenderer (typeof (MyApp.TwitterButton), typeof (MyApp.iOS.CustomButtonRenderer))]

namespace MyApp.iOS
{
    using Xamarin.Forms.Platform.iOS;
    using Xamarin.Forms;

    public class CustomButtonRenderer : ButtonRenderer where T : Button
    {
        private IApplicationConfiguration applicationConfiguration = new ApplicationConfiguration();

        protected override void OnModelSet(VisualElement model)
        {
            base.OnModelSet (model);

            var customButton = (T) Control;
            customButton.Initialise ((float)model.Y, this.applicationConfiguration);
        }
    }
}

namespace MyApp.iOS
{
    using MonoTouch.UIKit;

    public sealed class ConfirmButton : Button
    {
        public new void Initialise (float y, IApplicationConfiguration appSettings)
        {
            base.Initialise(y, appSettings);
            this.SetBackgroundImage(UIImage.FromBundle("btn-ok"), UIControlState.Normal);
        }
    }
}

namespace MyApp.iOS
{
    using System.Drawing;
    using MonoTouch.UIKit;

    public class Button : UIButton
    {
        public void Initialise (float verticalPosition, IApplicationConfiguration appSettings)
        {
            this.Frame = new RectangleF (
                appSettings.ButtonHorizontalPosition, 
                verticalPosition,
                appSettings.ButtonWidth, 
                appSettings.ButtonHeight);

            this.Font = UIFont.FromName (appSettings.Font, appSettings.FontSize);
        }
    }
}

As I was writing it I got really excited as I anticipated having discovered a shortcut to writing custom renderers for each button type. My excitement was shortlived however as when I ran it, it failed with "Unable to cast object of type 'MonoTouch.UIKit.UIButton' to type 'T'."

I'm holding onto a glimmer of hope that it might be possible and that I'm not flogging a dead horse, so thought I should ask the experts before I spend too much time on it.

So the TLDR is: are generic custom renderers possible? If so, how wide of the mark am I in getting my implementation working. Bonus points for sending me in the right direction!

Thanks in advance, folks. Any guidance much appreciated.

Best Answer

Answers

  • ScottBradleyScottBradley AUMember ✭✭

    This doesn't answer your question directly but have you considered using Pixate?

    http://components.xamarin.com/view/PixateFreestyle

  • MichaelRidlandMichaelRidland AUInsider, University ✭✭✭

    Hi

    You should be able to do this but your code isn't correct as your cross platform classes are referencing platform specific code.

    See how in Xamarin example the MyEntry doesn't have any platform specific code.

    public class MyEntry : Entry {}

    http://developer.xamarin.com/guides/cross-platform/xamarin-forms/custom-renderer/

    You can also check out how I did it on this blog/video.

    Thanks

  • bitsprintbitsprint GBUniversity

    Hi,

    Thanks for the suggestions.

    Scott - I may have a look at Pixate if I can't get this to work.

    Michael - I've updated the code and added the code for the one of the buttons from the shared project, which doesn't have platform specific code:

    Stephane - The type mismatch is between the MonoTouch.UIKit.UIButton and my own, but this is where the problem is - I'm trying to cast from a base type (UIButton) to a derived type (MyApp.iOS.EmailButton)!

    namespace MyApp
    {
        using Xamarin.Forms;
    
        public class EmailButton : Button
        {
        }
    }
    
    [assembly: Xamarin.Forms.ExportRenderer (typeof (MyApp.ActionButton), typeof (MyApp.iOS.CustomButtonRenderer< MyApp.iOS.ActionButton >))]
    [assembly: Xamarin.Forms.ExportRenderer (typeof (MyApp.ConfirmButton), typeof (MyApp.iOS.CustomButtonRenderer< MyApp.iOS.ConfirmButton >))]
    [assembly: Xamarin.Forms.ExportRenderer (typeof (MyApp.EmailButton), typeof (MyApp.iOS.CustomButtonRenderer< MyApp.iOS.EmailButton >))]
    
    namespace MyApp.iOS
    {
        using MonoTouch.UIKit;
    
        using Xamarin.Forms.Platform.iOS;
        using Xamarin.Forms;
    
        public class CustomButtonRenderer : ButtonRenderer where T : MyApp.iOS.Button
        {
            private IApplicationConfiguration applicationConfiguration = new ApplicationConfiguration();
    
            protected override void OnModelSet(VisualElement model)
            {
                base.OnModelSet (model);
    
                var customControl = (T) Control;
                customControl.Initialise ((float)model.Y, this.applicationConfiguration);
            }
        }
    }
    
    namespace MyApp.iOS
    {
        using System.Drawing;
        using MonoTouch.UIKit;
    
        public class Button : UIButton
        {
            public void Initialise (float verticalPosition, IApplicationConfiguration appSettings)
            {
                this.Frame = new RectangleF (
                    appSettings.ButtonHorizontalPosition, 
                    verticalPosition,
                    appSettings.ButtonWidth, 
                    appSettings.ButtonHeight);
    
                this.Font = UIFont.FromName (appSettings.Font, appSettings.FontSize);
            }
        }
    }
    
    namespace MyApp.iOS
    {
        using MonoTouch.UIKit;
    
        public class EmailButton : MyApp.iOS.Button
        {
            public new void Initialise (float y, IApplicationConfiguration appSettings)
            {
                base.Initialise (y, appSettings);
                this.SetBackgroundImage(UIImage.FromBundle("btn-email"), UIControlState.Normal);
                this.ContentEdgeInsets = new UIEdgeInsets (0, 40, 0, 0);
            }
        }
    }
    

    On this occasion I don't think I'll be able to use a generic solution. If I could new up one of my button instances and set the value of Control in the renderer it might be possible but it looks like you can't set the value of Control.

Sign In or Register to comment.