Bug 53271 - CLKTextProvider textProviderWithFormat is not provided
Summary: CLKTextProvider textProviderWithFormat is not provided
Status: CONFIRMED
Alias: None
Product: iOS
Classification: Xamarin
Component: Xamarin.WatchOS.dll ()
Version: master
Hardware: PC Mac OS
: Normal enhancement
Target Milestone: Untriaged
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2017-03-12 23:53 UTC by t9mike
Modified: 2017-03-23 18:23 UTC (History)
3 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report for Bug 53271 on Developer Community or GitHub if you have new information to add and do not yet see a matching new report.

If the latest results still closely match this report, you can use the original description:

  • Export the original title and description: Developer Community HTML or GitHub Markdown
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments

Related Links:
Status:
CONFIRMED

Description t9mike 2017-03-12 23:53:43 UTC
Help! You do not have a binding for CLKTextProvider textProviderWithFormat()

CLKTimeTextProvider, CLKRelativeDateTextProvider, et all are fairly useless without this as you would want to create compound text string such as "Launch in 1:23" where 1:23 is a timespan that WatchKit will update based on provided fixed date.

** I cannot continue work on my complication without this. **

You expose localizable versions but not the important one:

https://github.com/xamarin/xamarin-macios/blob/master/src/clockkit.cs

		// FIXME: expose gracefully <--- YES **PLEASE** FIX THIS ASAP BUT CALL IT CreateWithFormat()
		[Static, Internal]
		[Export ("textProviderWithFormat:", IsVariadic = true)]
		CLKTextProvider Create (string format, IntPtr varArgs);

		[Watch (3,0)]
		[Static]
		[Export ("localizableTextProviderWithStringsFileTextKey:")]
		CLKTextProvider CreateLocalizable (string textKey);

		[Watch (3,0)]
		[Static]
		[Export ("localizableTextProviderWithStringsFileTextKey:shortTextKey:")]
		CLKTextProvider CreateLocalizable (string textKey, [NullAllowed] string shortTextKey);

		[Watch (3,0)]
		[Static]
		[Export ("localizableTextProviderWithStringsFileFormatKey:textProviders:")]
		CLKTextProvider CreateLocalizable (string formatKey, CLKTextProvider[] textProviders);


I guess following your naming function it should be CreateWithFormat. I do not think Create() is specific enough.
Comment 1 t9mike 2017-03-13 03:31:52 UTC
I fixed this in a checkout of cycle9 branch and I see my fix in XS (code completion). And I get a compile. But I don't see XS deploy my app to simulator. It just does build and stops.

I hope you will prioritize getting this fix into Alpha channel:

[Watch (2,0)]
[Static]
[Export ("textProviderWithFormat:")]
CLKTextProvider CreateWithFormat (string format, CLKTextProvider[] textProviders);
Comment 2 Sebastien Pouliot 2017-03-13 19:51:28 UTC
Binding variadic methods (across all architectures) is tricky and why this member is not yet exposed publicly. We're working on a (arch) transparent solution but it is not ready yet.

While not identical, and a bit more work, I think you should go thru the localization route.

Note that your definition from comment #1:

> CLKTextProvider CreateWithFormat (string format, CLKTextProvider[] textProviders);

is wrong as it does not match the native signature. It might work in some case, if you're lucky and the ABI match, but depending on the types being used you can end up with a broken stack.

> I guess following your naming function it should be CreateWithFormat.

No, we _try_ to avoid having the method name include any of the parameter (or type) names.
Comment 3 t9mike 2017-03-13 20:03:05 UTC
Thank you for reply. I'll try localized route.
Comment 4 Sebastien Pouliot 2017-03-13 20:04:28 UTC
No problem :) If that does not work then there are other alternatives we can suggest (until this is properly fixed).
Comment 5 t9mike 2017-03-14 02:50:46 UTC
I cannot get localized provider of any sort to work. 

I tried combinations of Base.lproj/ckcomplication.strings and Base.lproj/Localizable.strings in the root of the watch extension and under watch  Resources. In all cases build type of the strings file was BundleResource.

To KISS I tried simple provider (vs. my compound):

tempplate.Body1TextProvider = CLKTextProvider.CreateLocalizable("Test");

where strings file has "Test" = "Hello World";

All I get is the key, not value.

The example I found at https://github.com/LoopKit/Loop/tree/master/WatchApp%20Extension looks similar.
Comment 6 t9mike 2017-03-14 22:26:24 UTC
Sebastien, can you provide sample project of using the localized provider? I want to continue working on my extension and this is last piece. Alternatively, how to create private binding for my use. Thank you.
Comment 7 Manuel de la Peña [MSFT] 2017-03-15 17:25:41 UTC
Hello, 

Can you provide the exact signature of the method that you need to call so that I can give you workaround.
Comment 8 t9mike 2017-03-15 17:41:39 UTC
I will want to call three ways:

template.TextProvider = CLKTextProvider.Create(string,CLKRelativeDateTextProvider)
template.TextProvider = CLKTextProvider.Create(string,CLKRelativeDateTextProvider, CLKTimeTextProvider)
template.TextProvider = CLKTextProvider.Create(string,CLKTimeTextProvider)

Thank you!
Comment 9 t9mike 2017-03-17 12:45:19 UTC
Can I get an update and ETA on work around please?

I'm OK going the CLKTextProvider.CreateLocalizable() route if you can send working example. I could not get it to work on my end, but perhaps I was doing something wrong.
Comment 10 t9mike 2017-03-21 03:27:42 UTC
Hello Manuel and Sebastien. Can you give me an update when I can expect work around? Thank you.
Comment 11 Manuel de la Peña [MSFT] 2017-03-22 10:17:48 UTC
Hello,

The issue we have at the moment is getting such signatures working correctly with x64 yet, since you are working with the watch you can use the following PInvoke signature to create the text provider:

[DllImport (Constants.WatchKitLibrary)]
static extern IntPtr CLKTextProviderCreate (/* NSString* */ IntPtr format, __arglist);

This will allow you to create the provider in the following way:

using System;
using System.Runtime.InteropServices;
using ClockKit;
using Foundation;
using ObjCRuntime;

namespace WatchTest.MyWatch
{
    public static class CLKTextProviderCreator
    {
		
        [DllImport (Constants.WatchKitLibrary)]
        static extern IntPtr CLKTextProviderCreate (/* NSString* */ IntPtr format,
            __arglist);

        public static CLKTextProvider Create (NSString str,
                CLKRelativeDateTextProvider provider) {
            var ptr = CLKTextProviderCreate (str.Handle, __arglist (provider));
            return Runtime.GetNSObject<CLKTextProvider> (ptr);
        }

        public static CLKTextProvider Create (NSString str,
                CLKRelativeDateTextProvider dateProvider,
                CLKTimeTextProvider textProvider) {
            var ptr = CLKTextProviderCreate (str.Handle,
                __arglist (dateProvider.Handle, textProvider.Handle));
            return Runtime.GetNSObject<CLKTextProvider> (ptr);
        }

        public static CLKTextProvider Create (NSString str,
                CLKTimeTextProvider textProvider) {
            var ptr = CLKTextProviderCreate (str.Handle,
                __arglist (textProvider.Handle));
            return Runtime.GetNSObject<CLKTextProvider> (ptr);
        }
    }
}

__arglist is not documented and does not work well with x64 but it is not your case. In the future we will provide a correct binding for this methods.
Comment 12 Manuel de la Peña [MSFT] 2017-03-22 10:19:12 UTC
Please let me know if the above allows you to move fwd. Will leave the bug open since is something we should correctly fix.
Comment 13 Manuel de la Peña [MSFT] 2017-03-22 10:33:32 UTC
Ok, the above will work in the simulator, not with the aot.. let me find a diff approach
Comment 14 Manuel de la Peña [MSFT] 2017-03-22 10:40:27 UTC
The other approach is to create a method that will create the object for you in objc, and then create a binding for each of the method you need:

[DllImport (Constants.WatchKitLibrary)]
static extern IntPtr CLKTextProviderCreateDateProvider (/* NSString* */ IntPtr format, IntPtr arg0)
[DllImport (Constants.WatchKitLibrary)]
static extern IntPtr CLKTextProviderCreateDateText (/* NSString* */ IntPtr format, IntPtr arg0, IntPtr arg1);

The above will work both in the SIM and in the device with the AOT. Sorry I forgot to the fact that AOT does not like __arglist. Mea culpa. Please ping me with any problems.
Comment 15 Manuel de la Peña [MSFT] 2017-03-22 11:21:23 UTC
Another approach can be found here: https://gist.github.com/mandel-macaque/8a45b680c6c8cdf3e2a8abbe0cfd95e9

This will use obj_msgSend to allow you to send the correct message to the object.
Comment 16 t9mike 2017-03-22 23:05:48 UTC
Thank you Manuel. I have used the objc_msgSend approach to create work around. Complete example below.

using System;
using System.Runtime.InteropServices;
using ClockKit;
using Foundation;
using ObjCRuntime;

namespace WatchExtension
{
    public static class TextProviderWithFormat
    {
        [DllImport("/usr/lib/libobjc.dylib")]
        static extern IntPtr objc_msgSend(IntPtr obj, IntPtr selector, IntPtr format, IntPtr provider0);

        [DllImport("/usr/lib/libobjc.dylib")]
        static extern IntPtr objc_msgSend(IntPtr obj, IntPtr selector, IntPtr format, IntPtr provider0, IntPtr provider1);

        public static CLKTextProvider Create(string format,
                CLKRelativeDateTextProvider provider)
        {
            IntPtr ptr = objc_msgSend(new Class(typeof(CLKTextProvider)).Handle,
                                      Selector.GetHandle("textProviderWithFormat:"), 
                                      new NSString(format).Handle, provider.Handle);
            return Runtime.GetNSObject<CLKTextProvider>(ptr);
        }

        public static CLKTextProvider Create(string format,
            CLKTextProvider textProvider,
            CLKRelativeDateTextProvider relativeDateTextProvider)
        {
            IntPtr ptr = objc_msgSend(new Class(typeof(CLKTextProvider)).Handle,
                                      Selector.GetHandle("textProviderWithFormat:"),
                                      new NSString(format).Handle, 
                                      textProvider.Handle,
                                      relativeDateTextProvider.Handle);
            return Runtime.GetNSObject<CLKTextProvider>(ptr);
        }
    }
}
Comment 17 t9mike 2017-03-22 23:56:36 UTC
For completeness, here is another binding I'll be using:

        public static CLKTextProvider Create(string format,
            CLKRelativeDateTextProvider relativeDateTextProvider,
            CLKTimeTextProvider timeTextProvider)
        {
            IntPtr ptr = objc_msgSend(new Class(typeof(CLKTextProvider)).Handle,
                                      Selector.GetHandle("textProviderWithFormat:"),
                                      new NSString(format).Handle,
                                      relativeDateTextProvider.Handle,
                                      timeTextProvider.Handle);
            return Runtime.GetNSObject<CLKTextProvider>(ptr);
        }
Comment 18 Manuel de la Peña [MSFT] 2017-03-23 18:23:31 UTC
Superb, I'm glad the workaround unblocks you. We will fix this kind of APIs asap and will close the bug once it has been done.