Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UIAppearance appearanceWhenContainedInInstancesOfClasses incorrectly restricted to tvOS #3230

Open
sushihangover opened this issue Jan 15, 2018 · 3 comments
Labels
enhancement The issue or pull request is an enhancement good first issue This is a good first issue for someone to start working with our code iOS Issues affecting Xamarin.iOS missing-api-bindings
Milestone

Comments

@sushihangover
Copy link

sushihangover commented Jan 15, 2018

Note: This is a C/P from my StackOverflow answer, so if you need additional info, just ask, thanks.

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 via a #if TVOS and that is just not true.

Re: Apple Docs: https://developer.apple.com/documentation/uikit/uiappearance/1615013-appearancewhencontainedininstanc

#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 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);
var searchBar = new UISearchBar(new CGRect(0, 50, 300, 40));
searchBar.Placeholder = "Xamarin.iOS";
Add(searchBar);

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;
}

Xamarin.iOS Version: 11.8.0.1

@timrisi timrisi added the iOS Issues affecting Xamarin.iOS label Jan 16, 2018
@timrisi timrisi added this to the Future milestone Jan 16, 2018
@rolfbjarne rolfbjarne added the enhancement The issue or pull request is an enhancement label Sep 3, 2018
@justin-caldicott
Copy link

Hmm, seems like a bug that there are no bindings for this for iOS. Any plans to fix it? It's been two years. :)

@tipa
Copy link

tipa commented Sep 16, 2021

would love to see this fixed as well!

@amirvenus
Copy link

Hmm, seems like a bug that there are no bindings for this for iOS. Any plans to fix it? It's been two years. :)

Soon 4 years and it hasn't been fixed. How lovely!

@rolfbjarne rolfbjarne added good first issue This is a good first issue for someone to start working with our code missing-api-bindings labels Sep 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement The issue or pull request is an enhancement good first issue This is a good first issue for someone to start working with our code iOS Issues affecting Xamarin.iOS missing-api-bindings
Projects
None yet
Development

No branches or pull requests

6 participants