Search code examples
c#ios5xamarin.iosuibuttonuiappearance

How to create a UIAppearance proxy for an inherited view in MonoTouch?


I created a subclass of UIButton which acts as a custom Back button inside UIBarButtonItem:

 - - - - - - - - - - - - - - - - - 
|         UIBarButtonItem         |

|     ------------------------    |
     /                       |
|    \ BackButton : UIButton |    |
      ------------------------
|  _  _  _  _  _  _  _  _  _  _  _|

As I was switching my code to using UIAppearance, I noticed that BackButton.Appearance is, unsuprisingly, inherited from UIButton.Appearance, and therefore changing it will change all UIButtons throughout the application and not just my custom buttons.

I know I can use AppearanceWhenContainedIn but, because UIBarButtonItem is not an appearance container, I'd have to insert another view between them to recognize back buttons, which I don't want to do.

How do I provide a UIAppearance proxy for my custom UIButton subclass that has all the methods available in UIButton.UIButtonAppearance? I noticed that its constructor is internal.


Solution

  • Although there may be a simpler answer, I ended up inheriting from UIAppearance directly and copy-pasting relevant piece of UIButton and UIButton+UIButtonAppearance binding implementation from MonoDevelop C# disassembler and fixing internal fields here and there.

    Inside BackButton:

    static readonly IntPtr class_ptr = Class.GetHandle(typeof(BackButton));
    
    public new static BackButtonAppearance Appearance
    {
        get {
            return new BackButtonAppearance (Messaging.IntPtr_objc_msgSend (class_ptr, UIAppearance.SelectorAppearance));
        }
    }
    
    public new static BackButtonAppearance AppearanceWhenContainedIn (params Type[] containers)
    {
        return new BackButtonAppearance (UIAppearance.GetAppearance (class_ptr, containers));
    }
    

    Nested BackButtonAppearance:

    public class BackButtonAppearance : UIAppearance {
        public BackButtonAppearance(IntPtr handle) : base(handle) { }
    
        // These are copied from UIButton disassembly:
    
        static readonly IntPtr selSetBackgroundImageForState_ = Selector.GetHandle ("setBackgroundImage:forState:");
        static readonly IntPtr selSetTitleShadowColorForState_ = Selector.GetHandle ("setTitleShadowColor:forState:");            
        static readonly IntPtr selSetTitleColorForState_ = Selector.GetHandle ("setTitleColor:forState:");
    
        // These are copied from UIButton+UIButtonAppearance disassembly,
        // with internal UIButton.selSet* fields replaced by fields declared above.
    
        [Export ("setBackgroundImage:forState:"), CompilerGenerated]
        public virtual void SetBackgroundImage (UIImage image, UIControlState forState)
        {
            UIApplication.EnsureUIThread ();
            if (this.IsDirectBinding) {
                Messaging.void_objc_msgSend_IntPtr_UInt32 (base.Handle, selSetBackgroundImageForState_, (image != null) ? image.Handle : IntPtr.Zero, (uint)forState);
            } else {
                Messaging.void_objc_msgSendSuper_IntPtr_UInt32 (base.SuperHandle, selSetBackgroundImageForState_, (image != null) ? image.Handle : IntPtr.Zero, (uint)forState);
            }
        }
    
    
        [Export ("setTitleColor:forState:"), CompilerGenerated]
        public virtual void SetTitleColor (UIColor color, UIControlState forState)
        {
            UIApplication.EnsureUIThread ();
            if (this.IsDirectBinding) {
                Messaging.void_objc_msgSend_IntPtr_UInt32 (base.Handle, selSetTitleColorForState_, (color != null) ? color.Handle : IntPtr.Zero, (uint)forState);
            } else {
                Messaging.void_objc_msgSendSuper_IntPtr_UInt32 (base.SuperHandle, selSetTitleColorForState_, (color != null) ? color.Handle : IntPtr.Zero, (uint)forState);
            }
        }
    
        [Export ("setTitleShadowColor:forState:"), CompilerGenerated]
        public virtual void SetTitleShadowColor (UIColor color, UIControlState forState)
        {
            UIApplication.EnsureUIThread ();
            if (this.IsDirectBinding) {
                Messaging.void_objc_msgSend_IntPtr_UInt32 (base.Handle, selSetTitleShadowColorForState_, (color != null) ? color.Handle : IntPtr.Zero, (uint)forState);
            } else {
                Messaging.void_objc_msgSendSuper_IntPtr_UInt32 (base.SuperHandle, selSetTitleShadowColorForState_, (color != null) ? color.Handle : IntPtr.Zero, (uint)forState);
            }
        }
    }
    

    This allows me to bring my custom view to support UIAppearance-style API.