How do I read a native API property on iOS

I would like to access [[UIApplication sharedApplication]scheduledLocalNotifications] and I am guessing I need to create a uno wrapper to expose it to Javascript. How would one accomplish this?

Hey Sebastian!

You would access the function from Uno using foreign code, and expose it to Javascript using a native module. Let us know if you have any further questions or if you get stuck. :slight_smile:

Cheers,
Olle

Thanks, I’ll check it out!

I’ve read the docs and looked at some examples. Total Obj-C n00b so might be me. I can get a stub working in Preview, but I get stuck with the iOS implementation:

using Fuse;
using Fuse.Scripting;
using Fuse.Reactive;
using Uno.UX;
using Uno.Compiler.ExportTargetInterop;

// [ForeignInclude(Language.ObjC, "Example.hh")]

[UXGlobalModule]
public class LocalNotificationsExtensionModule : NativeModule
{
    static readonly LocalNotificationsExtensionModule _instance;

    public LocalNotificationsExtensionModule()
    {
        // Make sure we're only initializing the module once
        if(_instance != null) return;

        _instance = this;
        Resource.SetGlobalKey(_instance, "LocalNotificationsExtensionModule");
        AddMember(new NativeFunction("scheduledLocalNotifications", (NativeCallback)scheduledLocalNotifications));
    }


    [Foreign(Language.ObjC)]
    public static extern(iOS) object scheduledLocalNotifications(Context c, object[] args)
    @{
        return [[UIApplication sharedApplication]scheduledLocalNotifications];
    @}

    public static extern(!(iOS || Android)) object scheduledLocalNotifications(Context c, object[] args)
    {
        return 10;
    }

}

I get:

/Users/jhsware/Fuse/Veckoappen/build/iOS/Preview/src/_root.LocalNotificationsExtensionModule.mm:105:20: Cannot initialize return object of type 'id<UnoObject>' with an rvalue of type 'NSArray<UILocalNotification *> * _Nullable'

All I really want in the end is the count. But appears that I am getting an NSArray and I can’t figure out how I specify NSArray as the return type of the method.

Hey again!

If you just want the count, I suggest just returning that as an int from the method.

Returning the array itself will require a little more work: The first problem is that Uno doesn’t know what an Objective-C NSArray is, so it will either have to be returned as an ObjC.Object, or be converted to some type that Uno knows about (e.g. using UXL macros) such as an array or a List. By specifying object as the return type of the foreign method, you are saying that it will return an Uno object, which it is not in this case. The second problem will be to convert the object to something that is usable from JavaScript. You might want to use a Scripting.Array which can be created using Context.NewArray (see here).

If you want to avoid the complications that the conversions bring a general strategy is to do more of the extraction work in Objective-C and to return primitive types or strings as often as possible, since those types are automatically converted both in the ObjC->Uno layer and the Uno->JS layer.

I have been trying to return an int, but I get a compilation error, but I am not sure what is wrong:

lib/LocalNotification.uno(21.85): E2029: '<method_group>' has no defined cast to type Fuse.Scripting.NativeCallback
/Users/jhsware/Fuse/Veckoappen/lib/LocalNotification.uno(21,86,21,113): Error E2029: '<method_group>' has no defined cast to type Fuse.Scripting.NativeCallback
    [Foreign(Language.ObjC)]
    public static extern(iOS) int scheduledLocalNotifications(Context c, object[] args)
    @{
        return [[[UIApplication sharedApplication] scheduledLocalNotifications] count];
    @}

The rest of the code is the same as above

Hey!

The error message says that the method passed to AddMember must match the signature of the NativeCallback delegate. In this case it complains because the return type must be object. Try creating a wrapper like the following:

...
AddMember(new NativeFunction("scheduledLocalNotifications", (NativeCallback)Wrapper));
...

static object Wrapper(Context c, object[] args)
{
    return scheduledLocalNotifications();
}

[Foreign(Language.ObjC)]
static extern(iOS) int scheduledLocalNotifications()
@{
    return [[[UIApplication sharedApplication] scheduledLocalNotifications] count];
@}

Making progress! Now my only remaining issue is that the module isn’t included when building for iOS. I have done uno clean and believe I have specified all things in lib/ to be bundled. It works with local preview, but not when doing fuse build . -t ios --run:

Error: JavaScript error in generateBadgeUpdates line -1. require(): module not found: LocalNotificationsExtensionModule in Fuse.Scripting.ScriptModule.RequireContext</usr/local/share/uno/Packages/Fuse.Scripting/0.35.12/$.uno:214>

.unoproj

{
  "RootNamespace": "",
  "Packages": [
    "Fuse",
    "Fuse.Storage",
    "Fuse.Scripting",
    "Fuse.Reactive",
    "FuseJS",
    "Fuse.LocalNotifications"
  ],
  "Includes": [
    "*",
    "*.js:Bundle",
    "lib/*:Bundle",
    "Assets/mockContent/*:Bundle"
  ],
  "Projects": [
    "NPM-Packages/Veckoappen_modules.unoproj"
  ],
  "Excludes": [
    "node_modules",
    "NPM-Packages"
  ]
}

lib/LocalNotification.uno

using Fuse;
using Fuse.Scripting;
using Fuse.Reactive;
using Uno.UX;
using Uno.Compiler.ExportTargetInterop;

// [ForeignInclude(Language.ObjC, "Example.hh")]

[UXGlobalModule]
public class LocalNotificationsExtensionModule : NativeModule
{
    static readonly LocalNotificationsExtensionModule _instance;

    public LocalNotificationsExtensionModule()
    {
        // Make sure we're only initializing the module once
        if(_instance != null) return;

        _instance = this;
        Resource.SetGlobalKey(_instance, "LocalNotificationsExtensionModule");
        
        AddMember(new NativeFunction("scheduledLocalNotifications", (NativeCallback)scheduledLocalNotificationsWrapper));
    }

    static object scheduledLocalNotificationsWrapper(Context c, object[] args)
    {
        return scheduledLocalNotifications();
    }


    [Foreign(Language.ObjC)]
    static extern(iOS) int scheduledLocalNotifications()
    @{
        return [[[UIApplication sharedApplication] scheduledLocalNotifications] count];
    @}

    static extern(!(iOS || Android)) int scheduledLocalNotifications()
    {
        return 10;
    }

}

Uno files should be included as Source in the unoproj so that they’re actually compiled. If you leave it unspecified it will default to Source, but right now you’re specifying Bundle. So for example try explicitly adding "lib/LocalNotification.uno" to the Includes section.

Just to round off this thread… it works perfectly now! Thanks a lot!

Adding the wrapper example to https://www.fusetools.com/docs/native-interop/foreign-code

and how to register the file properly to https://www.fusetools.com/docs/native-interop/native-js-modules would be great. Even though it probably can be found in other places it would be useful not having to sift through everything available for simple stuff.