dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.27k stars 1.76k forks source link

Custom Renderers (iOS) not working in .Net MAUI. #12509

Open divyesh008 opened 1 year ago

divyesh008 commented 1 year ago

Description

I want to create a CustomDatePicker that supports nullable date and also provides a Clear action button. So that if users want to clear the selection they can. I have done R&D and found that we can use Xamarin.Forms renderers in .net Maui from here: https://github.com/dotnet/maui/wiki/Using-Custom-Renderers-in-.NET-MAUI

For the same requirements, I have got a solution and sample project posted by Charlin Agramonte (below link): https://xamgirl.com/clearable-datepicker-in-xamarin-forms/

Implemented same logic in .Net MAUI project. It does work fine in Android but in iOS custom renderer this.Control gives null.

Steps to Reproduce

  1. Create new MAUI project
  2. Create custom renderers for Android and iOS
  3. Add debug point to iOS renderer
    • Here you will notice that this.Control is null.
Screenshot 2023-01-09 at 4 44 33 PM

Link to public reproduction project repository

https://github.com/divyesh008/CustomRenderers_MAUI

Version with bug

6.0.312

Last version that worked well

6.0.312

Affected platforms

iOS

Affected platform versions

iOS 15

Did you find any workaround?

No

Relevant log output

No response

jfversluis commented 1 year ago

Is there any specific reason you're not using the handlers/mappers? Also you mention version 6.0.312, is that correct? Does it work when you update to the latest version (.NET 7)?

ghost commented 1 year ago

Hi @divyesh008. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

divyesh008 commented 1 year ago

@jfversluis It's just because I want to utilize what I already have. We can use Renderers still in MAUI (https://github.com/dotnet/maui/wiki/Using-Custom-Renderers-in-.NET-MAUI), right?

Tbh I have tried to convert it to a handler but did not get an idea of how to do that for this requirement. From the document, I was not getting a clear idea.

And yes with .Net 7 also it's not working.

divyesh008 commented 1 year ago

@jfversluis I have used handlers/mappers now.

public class NullableDatePickerRenderer : DatePickerHandler
{
    protected override MauiDatePicker CreatePlatformView()
    {
        return base.CreatePlatformView();
    }

    protected override void ConnectHandler(MauiDatePicker platformView)
    {
        var datePicker = VirtualView as NullableDatePicker;
        var platformPicker = new MauiDatePicker();

        var originalToolbar = platformPicker.InputAccessoryView as UIToolbar;

        if (originalToolbar != null && originalToolbar.Items.Length <= 2)
        {
            var clearButton = new UIBarButtonItem("Clear", UIBarButtonItemStyle.Plain, ((sender, ev) =>
            {
                datePicker.Unfocus();
                datePicker.Date = DateTime.Now;
                datePicker.CleanDate();
            }));

            var newItems = new List<UIBarButtonItem>();
            foreach (var item in originalToolbar.Items)
            {
                newItems.Add(item);
            }

            newItems.Insert(0, clearButton);

            originalToolbar.Items = newItems.ToArray();
            originalToolbar.SetNeedsDisplay();

            platformView.InputAccessoryView = originalToolbar;
            platformView.ReloadInputViews();

            base.ConnectHandler(platformView);
        }
    }   
}

With this, I have got the Clear button with the working condition but now the Done button has stopped working. On the Click of Done button, it should close the picker but that has stopped now.

jfversluis commented 1 year ago

In regard to the custom renderer, looking at your code it seems that you have missed this step to actually register the renderer: https://github.com/dotnet/maui/wiki/Using-Custom-Renderers-in-.NET-MAUI#configure-renderers

For the new approach, I'm not exactly sure what you're trying to do, this might help to get you started with customizing controls: https://learn.microsoft.com/dotnet/maui/user-interface/handlers/customize?view=net-maui-7.0

Can you confirm that, if you still want to use the custom renderer, it works when you actually register it? Customizing through handlers and mappers does work, implementing the right code for what you want to do is a bit beyond the scope of issues. For more "how to" like questions I would like to refer you to the Discussions or forums like Stack Overflow or Reddit.

ghost commented 1 year ago

Hi @divyesh008. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

divyesh008 commented 1 year ago

https://github.com/dotnet/maui/wiki/Using-Custom-Renderers-in-.NET-MAUI#configure-renderers

I have registered the renderer here using ConfigMobileUICoreAppBuilder():

Screenshot 2023-01-09 at 9 24 11 PM

So it's confirmed that the Renderer approach:

✅ Does work fine on Android ❌ Does not work on iOS

New Approach (Using Handlers/Mappers as you suggested)

It's not How to question, it shows which feature or function is still not working in .Net MAUI or the area of improvement.

PureWeen commented 1 year ago

@jfversluis I have used handlers/mappers now.

public class NullableDatePickerRenderer : DatePickerHandler
{
    protected override MauiDatePicker CreatePlatformView()
    {
        return base.CreatePlatformView();
    }

    protected override void ConnectHandler(MauiDatePicker platformView)
    {
        var datePicker = VirtualView as NullableDatePicker;
        var platformPicker = new MauiDatePicker();

        var originalToolbar = platformPicker.InputAccessoryView as UIToolbar;

        if (originalToolbar != null && originalToolbar.Items.Length <= 2)
        {
            var clearButton = new UIBarButtonItem("Clear", UIBarButtonItemStyle.Plain, ((sender, ev) =>
            {
                datePicker.Unfocus();
                datePicker.Date = DateTime.Now;
                datePicker.CleanDate();
            }));

            var newItems = new List<UIBarButtonItem>();
            foreach (var item in originalToolbar.Items)
            {
                newItems.Add(item);
            }

            newItems.Insert(0, clearButton);

            originalToolbar.Items = newItems.ToArray();
            originalToolbar.SetNeedsDisplay();

            platformView.InputAccessoryView = originalToolbar;
            platformView.ReloadInputViews();

            base.ConnectHandler(platformView);
        }
    }   
}

With this, I have got the Clear button with the working condition but now the Done button has stopped working. On the Click of Done button, it should close the picker but that has stopped now.

@divyesh008 can you rework the description of this issue or open a new issue with regards to what you are hitting with the handler?

My guess with your initial issue is that you weren't calling "SetNativeControl" the ViewRenderer and VisualElementRenderer classes don't automatically create the platform control for you

ghost commented 1 year ago

Hi @divyesh008. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

ghost commented 1 year ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.

divyesh008 commented 1 year ago

@PureWeen I have just tried with Handler. The original issue is Renderer is not working in iOS. And in iOS SetNativeControl was not required it was for Android, and in Android, it does work fine.

The issue is with iOS that => this.Control gives null in iOS renderer.

denhaandrei commented 1 year ago

@PureWeen I have just tried with Handler. The original issue is Renderer is not working in iOS. And in iOS SetNativeControl was not required it was for Android, and in Android, it does work fine.

The issue is with iOS that => this.Control gives null in iOS renderer.

Any updates?

@jfversluis I have used handlers/mappers now. With this, I have got the Clear button with the working condition but now the Done button has stopped working. On the Click of Done button, it should close the picker but that has stopped now.

You can use something like this to fix done button, but the issue with this.Control is still(:

var setButton = new UIBarButtonItem("Done", UIBarButtonItemStyle.Done, ((sender, ev) =>
{
   datePicker.Unfocus();
   datePicker.Format = datePicker._originalFormat;
}));

   var newItems = new List<UIBarButtonItem>();
   newItems.Add(clearButton);
   newItems.Add(originalToolbar.Items[0]);
   newItems.Add(setButton);
   originalToolbar.Items = newItems.ToArray();
ghost commented 1 year ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

Zhanglirong-Winnie commented 1 year ago

Verified this issue with Visual Studio Enterprise 17.7.0 Preview 2.0. Can repro on iOS platform with sample project. CustomRenderers_MAUI-main.zip Capture

akshayamantya commented 1 year ago

@jfversluis

I'm also facing issue with Custom Renderer and Handler.

I've tried reusing the custom renderer after upgrading to MAUI. It works fine in sample MAUI project. But not working when integrated in main project. Both the files have same code and also I've verified the registration of renderer in MauiProgram.

I later created Handler. Again it works fine in sample MAUI project. But not working when integrated in main project. Both the files have same code and also I've verified the registration of Handler in MauiProgram.

The repo of the sample project can be found with the following link: https://github.com/apy534/MAUI-Handler-test

stewartsims commented 2 weeks ago

OK folks I realise this is a bit off-topic. But for anyone trying to implement a 'nullable' / clearable date field, that works on iOS, here's a solution I've got. It builds on what others have done here using the 'Handler' approach:

(in this implementation the date is updated when the 'done' button is pressed, rather than it doing nothing as above)

public class DateFieldRenderer : DatePickerHandler
{
    protected override MauiDatePicker CreatePlatformView()
    {
        return base.CreatePlatformView();
    }

    protected override void ConnectHandler(MauiDatePicker platformView)
    {
        var datePicker = VirtualView as DateField;
        var platformPicker = new MauiDatePicker();

        var originalToolbar = platformPicker.InputAccessoryView as UIToolbar;

        if (originalToolbar != null && originalToolbar.Items.Length <= 2)
        {
            var clearButton = new UIBarButtonItem("Clear", UIBarButtonItemStyle.Plain, ((sender, ev) =>
            {
                datePicker.Date = DateTime.Now;
                datePicker.CleanDate();
                platformView.ResignFirstResponder();
            }));

            var setButton = new UIBarButtonItem("Done", UIBarButtonItemStyle.Done, ((sender, ev) =>
            {
                var inputView = platformView.InputView as UIDatePicker;
                datePicker.Date = (DateTime) inputView.Date;
                platformView.ResignFirstResponder();
            }));

            var newItems = new List<UIBarButtonItem>();
            newItems.Add(clearButton);
            newItems.Add(originalToolbar.Items[0]);
            newItems.Add(setButton);

            originalToolbar.Items = newItems.ToArray();
            originalToolbar.SetNeedsDisplay();

            platformView.InputAccessoryView = originalToolbar;
            platformView.ReloadInputViews();

        }
    }
}

If you want the default (null) date not to display the current date then you will also need the following in your MauiProgram class:

in the CreateMauiApp() method under the builder:

.ConfigureMauiHandlers((handlers) =>
{
    #if ANDROID
        handlers.AddHandler(typeof(DateField), typeof(mobile.Droid.DateFieldRenderer));
    #elif IOS
        handlers.AddHandler(typeof(DateField), typeof(mobile.iOS.DateFieldRenderer));
    #endif

    Microsoft.Maui.Handlers.DatePickerHandler.Mapper.AppendToMapping("NullableDateField", (handler, view) =>
    {
        if (view is DateField picker)
        {
            #if IOS
                SetNullableText((DateField)view, handler.PlatformView);
            #endif
        }
    });
});

private static void SetNullableText(DateField view, MauiDatePicker platformDatePicker)
{
    if (view.NullableDate == null)
        platformDatePicker.Text = string.Empty;
    else
        platformDatePicker.Text = view.Date.ToString("dd/MM/yyyy"); // Be careful - UK date format hard coded here it should probably use the format set on the date field instead
}

Hope this helps someone... seems like a pretty common use case for a date field - perhaps it could be implemented in the core of MAUI some day?