Xamarin.iOS UIApperance setDefaultTextAttributes - xamarin

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.

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
Update: I submitted a Github issue regarding this.
https://github.com/xamarin/xamarin-macios/issues/3230
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);
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;
}

Related

Broadcast Extension with ReplayKit Xamarin iOS not displaying device screen

I created a Xamarin forms project and added Broadcast Upload Extension and Extension UI and reference them into IOS Project also I followed the instructions in the doc here
https://learn.microsoft.com/en-us/xamarin/ios/platform/extensions#container-app-project-requirements
I uses agora for screen sharing but the result is the device share the camera instead of it's screen
I followed this sample
https://github.com/DreamTeamMobile/Xamarin.Agora.Samples/tree/master/ScreenSharing
and added a Dependency service Interface to invoke it from Xamarin forms all is working expect device share it's camera not it's screen
public class AgoraServiceImplementation : IAgoraService
{
private void JoiningCompleted(Foundation.NSString arg1, nuint arg2, nint arg3)
{
_myId = arg2;
_agoraEngine.SetEnableSpeakerphone(true);
JoinChannelSuccess((uint)_myId);
var bundle = NSBundle.MainBundle.GetUrlForResource("ScreenSharingIOSExtension", "appex", "PlugIns");
var frame = new CGRect(100, 100, 60, 60);
var broadcastPicker = new RPSystemBroadcastPickerView(frame);
var bundle2 = new NSBundle(bundle);
broadcastPicker.PreferredExtension = bundle2.BundleIdentifier;
var vc = Platform.GetCurrentUIViewController();
vc.Add(broadcastPicker);
}
public void StartShareScreen(string sessionId, string agoraAPI, string token, VideoAgoraProfile profile = VideoAgoraProfile.Portrait360P, bool swapWidthAndHeight = false, bool webSdkInteroperability = false)
{
_agoraDelegate = new AgoraRtcDelegate(this);
_agoraEngine = AgoraRtcEngineKit.SharedEngineWithAppIdAndDelegate(agoraAPI, _agoraDelegate);
_agoraEngine.EnableWebSdkInteroperability(webSdkInteroperability);
_agoraEngine.SetChannelProfile(ChannelProfile.LiveBroadcasting);
_agoraEngine.SetClientRole(ClientRole.Broadcaster);
//
_agoraEngine.EnableVideo();
if (!string.IsNullOrEmpty(AgoraSettings.Current.EncryptionPhrase))
{
_agoraEngine.SetEncryptionMode(AgoraSettings.Current.EncryptionType.GetModeString());
_agoraEngine.SetEncryptionSecret(helper.AgoraSettings.Current.EncryptionPhrase);
}
_agoraEngine.StartPreview();
Join();
}
private async void Join()
{
var token = await AgoraTokenService.GetRtcToken(helper.AgoraSettings.Current.RoomName);
if (string.IsNullOrEmpty(token))
{
//smth went wrong
}
else
{
_agoraEngine.JoinChannelByToken(token, AgoraSettings.Current.RoomName, null, 0, JoiningCompleted);
}
}
}
any help ?

Importing native methods in Xamarin.Mac

I'm looking for a way to use SCNetworkInterfaceCopyAll in my Xamarin.Mac app.
I've imported it
[DllImport("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration")]
public static extern IntPtr SCNetworkInterfaceCopyAll();
Then I get an array by calling var array = NSArray.ArrayFromHandle<NSObject>(pointer).
But can't figure out how to get values from the output array of SCNetworkInterface. I've tried to marshal it as
[StructLayout(LayoutKind.Sequential)]
public struct Test
{
IntPtr interface_type;
IntPtr entity_hardware;
}
And then call Marshal.PtrToStructure<Test>(i.Handle) but it gives random pointers instead of meaningful values.
Can you use the information provided from System.Net.NetworkInformation. NetworkInterface (or do you need actually need a SCNetworkInterface?)
Xamarin.Mac Example:
foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
{
if (nic.OperationalStatus == OperationalStatus.Up)
Console.WriteLine(nic.GetPhysicalAddress());
}
Looking at SCNetworkConfiguration.h it appears there are a number of C APIs you are to call on your IntPtr to retrieve the specific information you need.
CoreFoundation APIs often return "baton" pointers that you need to pass to other functions. Where did you see that struct definition?
You can use Objective-C methods in Xamarin to get the MAC Address as C# gives a different MAC Address:
[DllImport("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration")]
public static extern IntPtr SCNetworkInterfaceCopyAll();
[DllImport("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration")]
public static extern IntPtr SCNetworkInterfaceGetHardwareAddressString(IntPtr scNetworkInterfaceRef);
private string MacAddress()
{
string address = string.Empty;
using (var interfaces = Runtime.GetNSObject<NSArray>(SCNetworkInterfaceCopyAll()))
{
for (nuint i = 0; i < interfaces.Count; i++)
{
IntPtr nic = interfaces.ValueAt(i);
var addressPtr = SCNetworkInterfaceGetHardwareAddressString(nic);
address = Runtime.GetNSObject<NSString>(addressPtr);
if (address != null) break;
}
}
return address;
}

xamarin.ios binding Segment-Analytics errors

I am working on the iOSbinding for Segment/Analytics and meeting a block on this.
The oc code I cannot find a good idea to convert to C# code.
-(SEGReachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref;
when i converted code to below:
[Export("initWithReachabilityRef:")]
unsafe IntPtr Constructor(ref NetworkReachability #ref);
it happened a error:
Error CS0311: The type SystemConfiguration.NetworkReachability' cannot be used as type parameterT' in the generic type or method ObjCRuntime.Runtime.GetNSObject<T>(System.IntPtr)'. There is no implicit reference conversion fromSystemConfiguration.NetworkReachability' to `Foundation.NSObject' (CS0311) (Anayltics.IOS)
[Export ("initWithReachabilityRef:")]
[CompilerGenerated]
public SEGReachability (ref SCNetworkReachabilityRef #ref)
: base (NSObjectFlag.Empty)
{
IntPtr #refValue = IntPtr.Zero;
IsDirectBinding = GetType ().Assembly == global::ApiDefinition.Messaging.this_assembly;
if (IsDirectBinding) {
InitializeHandle (global::ApiDefinition.Messaging.IntPtr_objc_msgSend_ref_IntPtr (this.Handle, Selector.GetHandle ("initWithReachabilityRef:"), ref refValue), "initWithReachabilityRef:");
} else {
InitializeHandle (global::ApiDefinition.Messaging.IntPtr_objc_msgSendSuper_ref_IntPtr (this.SuperHandle, Selector.GetHandle ("initWithReachabilityRef:"), ref refValue), "initWithReachabilityRef:");
}
#ref = #refValue != IntPtr.Zero ? Runtime.GetNSObject<Anayltics.SCNetworkReachabilityRef> (#refValue) : null; //**--**error happened in this line.****
}
Any one can help me?

How can i use VALA delegates in GTK3 button callback?

I'm trying to understand Vala delegates with Gtk3.
I tested callback and lambda with no problem.
I wanna test a delegate callback, here my code :
using Gtk;
delegate void typeDelegate(Button button);
int main (string[] args) {
Gtk.init (ref args);
typeDelegate cb = cbLabelf;
var window = new Window ();
window.title = "First GTK+ Program";
window.border_width = 10;
window.window_position = WindowPosition.CENTER;
window.set_default_size (350, 70);
window.destroy.connect (Gtk.main_quit);
var button = new Button.with_label ("Click me!");
//button.clicked.connect (cb);
//button.clicked+= cb;
button.clicked.connect+=cb;
window.add (button);
window.show_all ();
Gtk.main ();
return 0;
}
void cbLabelf(Button button)
{
button.label = "tank yu";
}
I also red generated C code ( when i use lambda) to understand.
Here the compil error :
GTKsampleDelegate.vala:20.5-20.30: error: Arithmetic operation not supported for types Gtk.Button.clicked.connect' andtypeDelegate'
button.clicked.connect+=cb;
Well,
Seems that you want to get the intrinsic variable that holds the instance that emitted the signal, I find strange that vala doesn't let you use a delegate variable to obtain it via parameter, yet, you can use one of the forms below: using no delegation variable (A) or bypassing the error with a closure (B).
public class FooSignalClass : Object {
/* Gtk Button.clicked signal has the void f(void) signature */
public signal void on_foo ();
public void foo() {
on_foo();
}
}
public delegate void FooSignalFunc (FooSignalClass fooer);
void on_foo_handler (FooSignalClass fooer) {
long fooer_memory_address = (long)fooer;
GLib.message(#"fooer exists? $(fooer!=null).");
GLib.message(#"address=$fooer_memory_address.");
}
int main () {
var foo_signal = new FooSignalClass();
long fooer_memory_address = (long)foo_signal;
GLib.message(#"foo_signal address=$fooer_memory_address.");
/* Option A: Connect directly without the delegate variable */
foo_signal.on_foo.connect(on_foo_handler);
/* Option B: You cant use a delegate directly, bypass it with a closure */
FooSignalFunc func = on_foo_handler;
foo_signal.on_foo.connect((instance) => {
func(instance);
});
foo_signal.foo();
return 0;
}

How to make Xamarin.Mac app "Open at Login"?

I have a Xamarin.Mac app that needs to open automatically at login.
How do I have my application get this setting without having to manually click on it?
I can give you a hint how to do it programmatically.
For this approach you need to make use of calls to native libraries via DllImport.
Following code will give you an idea how to proceed:
//needed library
const string DllName = "/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices";
static LSSharedFileList ()
{
dllHandle = Dlfcn.dlopen (DllName, 0);
kLSSharedFileListSessionLoginItems = Dlfcn.GetStringConstant (dllHandle, "kLSSharedFileListSessionLoginItems");
kLSSharedFileListItemLast = Dlfcn.GetStringConstant (dllHandle, "kLSSharedFileListItemLast");
}
[DllImport(DllName)]
public static extern IntPtr LSSharedFileListCreate (
IntPtr inAllocator,
IntPtr inListType,
IntPtr listOptions);
[DllImport(DllName, CharSet=CharSet.Unicode)]
public static extern void CFRelease (
IntPtr cf
);
[DllImport(DllName)]
public extern static IntPtr LSSharedFileListInsertItemURL (
IntPtr inList,
IntPtr insertAfterThisItem,
IntPtr inDisplayName,
IntPtr inIconRef,
IntPtr inURL,
IntPtr inPropertiesToSet,
IntPtr inPropertiesToClear);
And here the actual snippet:
public static void EnableLogInItem ()
{
IntPtr pFileList = IntPtr.Zero;
IntPtr pItem = IntPtr.Zero;
try
{
pFileList = LSSharedFileListCreate (
IntPtr.Zero,
kLSSharedFileListSessionLoginItems,
IntPtr.Zero);
pItem = LSSharedFileListInsertItemURL (
pFileList,
kLSSharedFileListItemLast,
IntPtr.Zero,
IntPtr.Zero,
NSBundle.MainBundle.BundleUrl.Handle,
IntPtr.Zero,
IntPtr.Zero);
}
finally
{
CFRelease (pItem);
CFRelease (pFileList);
}
}
Please keep in mind that it is not the whole solution, it is just a snippet to put an app in the login items list. Of course you have to handle errors, check for IntPtr.Zero after every call and so on, but this should give you an idea how it works.
Hope that helps!
Since the LSSharedFileList library is not supported on Xamarin.Mac you have to create a dylib with Xcode and bind it in your Xamarin.Mac application.
1) Create a Dylib project on Xcode. Add This function:
-(BOOL)AddLoginItem:(NSString *) AppPath{
// Your have to send this string as argument(i.e: on a textbox, write: /Applications/Calculator.app/)
// Get Login Items
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
NSLog(#"[DEBUG]Your Application Path:%#",AppPath);
// Covert String to CFURLRef (It adds "file://" to your itemUrl1)
CFURLRef appUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:(NSString*) AppPath];
NSLog(#"[DEBUG] appUrl:%#", appUrl);
// Now we add the requested Login Item
LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL);
// Confirm that your path was correct and that you got a valid Login Item Reference
NSLog(#"[DEBUG]Item:%#", itemRef);
if (itemRef) CFRelease(itemRef);
// Your item just got added to the user's Login Items List
CFRelease(loginItems);
return true;
}
return false;
}
2) Create a Cocoa project with an TextBox, a Push button and their Actions and Outlets controls in it.
Use this link to help you with the C# bindings from a objective-c DLL.
3) On your Xamarin.Mac project, in MainWindow.cs, add the following code and make the necessary changes to fit on your code.
Don't forget to add your .Net Assembly reference, created on the previous link and also your: using DLLloginItem; line.
// Some variables
private string TestItemName;
private bool rem;
// Button Click
partial void AddItemButton (Foundation.NSObject sender) {
LoginItemsDLL loginItem = new LoginItemsDLL();
// Enter this string on your TextBox TxtName /Applications/Calculator.app/
TestItemName = TxtName.StringValue;
rem=loginItem.AddLoginItem(TestItemName);
Console.WriteLine(rem);
}
In order to get your application path entered, use another function that just receives the application name and returns its path into AddLoginItem argument. Hope it helps!

Resources