Search code examples
xamarinxamarin.iosuikituiappearance

Xamarin.iOS UIApperance setDefaultTextAttributes


I'm trying to figure out how to implement following code in Xamarin:

[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setDefaultTextAttributes:@{NSForegroundColorAttributeName:[UIColor greenColor]}];

But I cannot find a way to setDefaultTextAttributes on UIApperance class.


Solution

  • There are a number of missing UIAppearance features in Xamarin.iOS and in regards to your question, there is a missing API.

    • This is a bug, 🍣 I wrote my own UIAppearance.cs to add the missing features and correct the missing API and assume no other Xamarin.iOS coders really use the newer UIAppearance features as it has been broken since iOS 9 in Xamarin.

    First, appearanceWhenContainedIn is deprecated and you should be using appearanceWhenContainedInInstancesOfClasses instead.

    AppearanceWhenContainedIn - was deprecated in iOS 9 and is not recommended for use.

    Second, appearanceWhenContainedInInstancesOfClasses is incorrectly defined within Xamarin.iOS as only available in tvOS and that is just not true.

    #if TVOS
       // new in iOS9 but the only option for tvOS
       const string selAppearanceWhenContainedInInstancesOfClasses = "appearanceWhenContainedInInstancesOfClasses:";
       ~~~
    

    UIAppearance.cs#L77

    Thus it is not available via the Xamarin.iOS wrapper API, but of course is available directly from the ObjC runtime as such:

    var NSForegroundColorAttributeName = Dlfcn.GetStringConstant(UIKitLibraryHandle, "NSForegroundColorAttributeName");
    var defaultAttributes = NSDictionary.FromObjectsAndKeys(new NSObject[] { UIColor.Red }, new NSObject[] { NSForegroundColorAttributeName });
    var styleHandle = GetAppearanceEx(Class.GetHandle("UITextField"), typeof(UISearchBar));
    void_objc_msgSend_IntPtr(styleHandle, Selector.GetHandle("setDefaultTextAttributes:"), defaultAttributes.Handle);
    

    enter image description here

    The next problem there are a number of Xamarin.iOS methods marked internal that are needed for the above code to function, so some copy/paste/modify of some source is needed:

    public const string selAppearanceWhenContainedInInstancesOfClasses = "appearanceWhenContainedInInstancesOfClasses:";
    
    public static readonly IntPtr UIKitLibraryHandle = Dlfcn.dlopen("/System/Library/Frameworks/UIKit.framework/UIKit", 0);
    
    [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
    public static extern IntPtr IntPtr_objc_msgSend_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1);
    
    [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
    public static extern void void_objc_msgSend_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1);
    
    public static IntPtr GetAppearanceEx(IntPtr class_ptr, params Type[] whenFoundIn)
    {
        var ptrs = TypesToPointers(whenFoundIn);
        var handles = NSArray.FromIntPtrs(ptrs);
        using (var array = handles)
        {
            return IntPtr_objc_msgSend_IntPtr(class_ptr, Selector.GetHandle(selAppearanceWhenContainedInInstancesOfClasses), array.Handle);
        }
    }
    
    public static IntPtr[] TypesToPointers(Type[] whenFoundIn)
    {
        IntPtr[] ptrs = new IntPtr[whenFoundIn.Length];
    
        for (int i = 0; i < whenFoundIn.Length; i++)
        {
            if (whenFoundIn[i] == null)
                throw new ArgumentException(String.Format("Parameter {0} was null, must specify a valid type", i));
            if (!typeof(NSObject).IsAssignableFrom(whenFoundIn[i]))
                throw new ArgumentException(String.Format("Type {0} does not derive from NSObject", whenFoundIn[i]));
    
            var classHandle = Class.GetHandle(whenFoundIn[i]);
            if (classHandle == IntPtr.Zero)
                throw new ArgumentException(string.Format("Could not find the Objective-C class for {0}", whenFoundIn[i].FullName));
            ptrs[i] = classHandle;
        }
        return ptrs;
    }