question

IgorKravchenko-7896 avatar image
0 Votes"
IgorKravchenko-7896 asked WenyanZhang-MSFT commented

How to create ios handler?

I have created a Card control and handlers for it (CardHandler, CardHandler.Android and CardHandler.iOS). PlatformView in Android handler is Google.Android.Material.Card.MaterialCardView. In iOS handler PlatformView is UIView.
But something is wrong because I get next error in shared handler: Partial declarations of 'CardHandler' must not specify different base classes
In CardHandler.iOS I have next:

202330-ios-handler.png
Full code:

 public enum CardType
     {
         Elevated, Filled, Outlined
     }
    
     public interface ICard : IView, IPadding, IPaddingElement
     {
         View Content { get; set; }
         CardType Type { get; set; }
     }
    
     [ContentProperty(nameof(Content))]
     public class Card : View, ICard
     {
         public event EventHandler Clicked;
         public event EventHandler LongClicked;
    
         public static readonly BindableProperty PaddingProperty = PaddingElement.PaddingProperty;
    
         public static readonly BindableProperty ContentProperty =
             BindableProperty.Create(nameof(Content), typeof(View), typeof(Card), default(View), propertyChanged: OnContentChanged);
    
         public static readonly BindableProperty TypeProperty =
             BindableProperty.Create(nameof(CardType), typeof(CardType), typeof(ICard), CardType.Elevated);
    
         public static readonly BindableProperty ClickedCommandProperty =
             BindableProperty.Create(nameof(ClickedCommand), typeof(ICommand), typeof(ICard), default(ICommand));
    
         public static readonly BindableProperty LongClickedCommandProperty =
             BindableProperty.Create(nameof(LongClickedCommand), typeof(ICommand), typeof(ICard), default(ICommand));
    
         public static readonly BindableProperty ClickedCommandParameterProperty =
             BindableProperty.Create(nameof(ClickedCommandParameter), typeof(object), typeof(ICard), default);
    
         public static readonly BindableProperty LongClickedCommandParameterProperty =
             BindableProperty.Create(nameof(LongClickedCommandParameter), typeof(object), typeof(ICard), default);
    
         public object ClickedCommandParameter
         {
             get => GetValue(ClickedCommandParameterProperty);
             set => SetValue(ClickedCommandParameterProperty, value);
         }
    
         public object LongClickedCommandParameter
         {
             get => GetValue(LongClickedCommandParameterProperty);
             set => SetValue(LongClickedCommandParameterProperty, value);
         }
    
         public ICommand ClickedCommand
         {
             get => (ICommand)GetValue(ClickedCommandProperty);
             set => SetValue(ClickedCommandProperty, value);
         }
    
         public ICommand LongClickedCommand
         {
             get => (ICommand)GetValue(ClickedCommandProperty);
             set => SetValue(ClickedCommandProperty, value);
         }
    
         public Thickness Padding
         {
             get => (Thickness)GetValue(PaddingElement.PaddingProperty);
             set => SetValue(PaddingElement.PaddingProperty, value);
         }
    
         public CardType Type
         {
             get => (CardType)GetValue(TypeProperty);
             set => SetValue(TypeProperty, value);
         }
    
         public View Content
         {
             get => (View)GetValue(ContentProperty);
             set => SetValue(ContentProperty, value);
         }
    
         Thickness IPaddingElement.PaddingDefaultValueCreator() => new Thickness(double.NaN);
    
         static void OnContentChanged(BindableObject bindable, object oldValue, object newValue)
         {
             if (bindable is Card card)
                 card.OnBindingContextChanged();
         }
    
         void IPaddingElement.OnPaddingPropertyChanged(Thickness oldValue, Thickness newValue)
         {
         }
    
         public void SendClicked()
         {
             Clicked?.Invoke(this, EventArgs.Empty);
             if (ClickedCommand != null && ClickedCommand.CanExecute(ClickedCommandParameter))
                 ClickedCommand.Execute(ClickedCommandParameter);
         }
    
         public void SendLongClicked()
         {
             LongClicked?.Invoke(this, EventArgs.Empty);
             if (LongClickedCommand != null && LongClickedCommand.CanExecute(LongClickedCommandParameter))
                 LongClickedCommand.Execute(LongClickedCommandParameter);
         }
     }
    
     static class PaddingElement
     {
         public static readonly BindableProperty PaddingProperty =
             BindableProperty.Create(nameof(IPaddingElement.Padding), typeof(Thickness), typeof(IPaddingElement), default(Thickness),
                                     propertyChanged: OnPaddingPropertyChanged,
                                     defaultValueCreator: PaddingDefaultValueCreator);
    
         static void OnPaddingPropertyChanged(BindableObject bindable, object oldValue, object newValue)
         {
             ((IPaddingElement)bindable).OnPaddingPropertyChanged((Thickness)oldValue, (Thickness)newValue);
         }
    
         static object PaddingDefaultValueCreator(BindableObject bindable)
         {
             return ((IPaddingElement)bindable).PaddingDefaultValueCreator();
         }
    
         public static readonly BindableProperty PaddingLeftProperty =
             BindableProperty.Create("PaddingLeft", typeof(double), typeof(IPaddingElement), default(double),
                                     propertyChanged: OnPaddingLeftChanged);
    
         static void OnPaddingLeftChanged(BindableObject bindable, object oldValue, object newValue)
         {
             var padding = (Thickness)bindable.GetValue(PaddingProperty);
             padding.Left = (double)newValue;
             bindable.SetValue(PaddingProperty, padding);
         }
    
         public static readonly BindableProperty PaddingTopProperty =
             BindableProperty.Create("PaddingTop", typeof(double), typeof(IPaddingElement), default(double),
                                     propertyChanged: OnPaddingTopChanged);
    
         static void OnPaddingTopChanged(BindableObject bindable, object oldValue, object newValue)
         {
             var padding = (Thickness)bindable.GetValue(PaddingProperty);
             padding.Top = (double)newValue;
             bindable.SetValue(PaddingProperty, padding);
         }
    
         public static readonly BindableProperty PaddingRightProperty =
             BindableProperty.Create("PaddingRight", typeof(double), typeof(IPaddingElement), default(double),
                                     propertyChanged: OnPaddingRightChanged);
    
         static void OnPaddingRightChanged(BindableObject bindable, object oldValue, object newValue)
         {
             var padding = (Thickness)bindable.GetValue(PaddingProperty);
             padding.Right = (double)newValue;
             bindable.SetValue(PaddingProperty, padding);
         }
    
         public static readonly BindableProperty PaddingBottomProperty =
             BindableProperty.Create("PaddingBottom", typeof(double), typeof(IPaddingElement), default(double),
                                     propertyChanged: OnPaddingBottomChanged);
    
         static void OnPaddingBottomChanged(BindableObject bindable, object oldValue, object newValue)
         {
             var padding = (Thickness)bindable.GetValue(PaddingProperty);
             padding.Bottom = (double)newValue;
             bindable.SetValue(PaddingProperty, padding);
         }
     }

Shared handler:

 #if IOS || MACCATALYST
 using PlatformView = UIKit.UIView;
 #elif ANDROID
 using PlatformView = Google.Android.Material.Card.MaterialCardView;
 #elif NET || (NET6_0 && !IOS && !ANDROID && !TIZEN)
 using PlatformView = System.Object;
 #endif
 public interface ICardHandler : IViewHandler
     {
         new PlatformView PlatformView { get; }
     }
    
     public partial class CardHandler : ICardHandler
     {
         public static IPropertyMapper<ICard, ICardHandler> Mapper = new PropertyMapper<ICard, ICardHandler>(ViewMapper)
         {
             [nameof(ICard.Content)] = MapContent,
             [nameof(IPadding.Padding)] = MapPadding,
         };
    
         public CardHandler(IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper)
         {
         }
    
         public CardHandler() : base(Mapper)
         {
         }
     }

Android handler:

 public partial class CardHandler : ViewHandler<ICard, MaterialCardView>
     {
         private int GetLayoutResource()
         {
             switch (VirtualView.Type)
             {
                 case CardType.Elevated:
                     return Resource.Layout.ElevatedCard;
                 case CardType.Filled:
                     return Resource.Layout.FilledCard;
                 case CardType.Outlined:
                     return Resource.Layout.OutlinedCard;
             }
             return Resource.Layout.ElevatedCard;
         }
    
         protected override MaterialCardView CreatePlatformView()
         {
             LayoutInflater inflater = LayoutInflater.FromContext(Context);
             Android.Views.View view = inflater.Inflate(GetLayoutResource(), null);
             MaterialCardView card = (MaterialCardView)view;
             card.Clickable = true;
             card.Focusable = true;
             return card;
         }
    
         protected override void ConnectHandler(MaterialCardView platformView)
         {
             base.ConnectHandler(platformView);
             platformView.Click += OnClicked;
             platformView.LongClick += OnLongClicked;
         }
    
         protected override void DisconnectHandler(MaterialCardView platformView)
         {
             base.DisconnectHandler(platformView);
             platformView.Click -= OnClicked;
             platformView.LongClick -= OnLongClicked;
         }
    
         private void OnClicked(object sender, EventArgs e)
         {
             if (VirtualView is Card card)
                 card.SendClicked();
         }
    
         private void OnLongClicked(object sender, Android.Views.View.LongClickEventArgs e)
         {
             if (VirtualView is Card card)
                 card.SendLongClicked();
         }
    
         public static void MapContent(ICardHandler handler, ICard card)
         {
             if (card.Content == null)
                 return;
             handler.PlatformView.AddView(card.Content.ToPlatform(handler.MauiContext));
         }
    
         public static void MapPadding(ICardHandler handler, IPadding card)
         {
             Thickness padding = handler.MauiContext.Context.ToPixels(card.Padding);
             handler.PlatformView.SetContentPadding((int)padding.Left, (int)padding.Top, (int)padding.Right, (int)padding.Bottom);
         }
     }

iOS handler:

 public partial class CardHandler : ViewHandler<ICard, UIKit.UIView>
     {
         protected override UIKit.UIView CreatePlatformView()
         {
    
         }
     }

What is wrong here? Thanks.








dotnet-maui
ios-handler.png (21.7 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

WenyanZhang-MSFT avatar image
1 Vote"
WenyanZhang-MSFT answered WenyanZhang-MSFT commented

Hello,
I check your project, please try to remove your CardHandler.iOS.cs to Platforms->iOS, remove your CardHandler.android.cs to Platforms->Android and enable change the namespace once you see the popup dialog, then add CardHandler in MauiProgram.

 #if IOS
              Handlers.AddHandler(typeof(Card), typeof(CardHandler));
  #endif

As you @IgorKravchenko-7896 said, if you prefer to use handlers for one control in one folder, try to combine filename and folder multi-targeting. Thank you!


Best Regards,
Wenyan Zhang


If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Thank you. I prefer to use handlers for one control in one folder (filename and folder multi-targeting).
Like this (also saw it in MAUI sources):

203195-structure.png

I have checked how to do this here

Edited my project file and all works fine! (didn't test on iphone because have a trouble with profile on this project but build was successful)

Should I mark this as answer or you need to add details about multi-targeting to your original answer? Thanks.




0 Votes 0 ·
structure.png (8.7 KiB)
WenyanZhang-MSFT avatar image WenyanZhang-MSFT IgorKravchenko-7896 ·

I'm so glad you solved this issue. I have converted the comment to an answer, you could check it.

0 Votes 0 ·
WenyanZhang-MSFT avatar image
1 Vote"
WenyanZhang-MSFT answered WenyanZhang-MSFT converted comment to answer

Hi @IgorKravchenko-7896,

I use the following code for testing. I see there are three errors, the last one means that you haven't returned an instance. When I debug the app on my real physical device, the program runs without any problems, although there are still the other two errors in VS.
It seems a problem of VS, you could try to report it, see: https://docs.microsoft.com/en-us/visualstudio/ide/how-to-report-a-problem-with-visual-studio?view=vs-2022.
In addition, there is no card view on iOS platform like MaterialCardView, you have to customize the view according to your need.

 public partial class CardHandler : ViewHandler<ICard, UIView>
     {
         protected override UIView CreatePlatformView()
         {
             UIView view = new UIView();
             view.Frame = new CoreGraphics.CGRect(0, 0, 300, 300);
             view.BackgroundColor = UIColor.Yellow;
             return view;
         }
     }

Add Card in MainPage without any parameters for testing

  <local:Card ></local:Card>

Register Handler in MauiProgram

         builder.UseMauiApp<App>().ConfigureMauiHandlers(Handlers =>
         {
 #if IOS
             Handlers.AddHandler(typeof(Card), typeof(CardHandler));
 #endif
    
         });


Best Regards,
Wenyan Zhang


If the response is helpful, please click "Accept Answer" and upvote it.
Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

I have updated Visual Studio to latest version and created a new project but have same errors.
Here is my project.
If you will be not able to reproduce it on your side I would report this.
Thanks.


0 Votes 0 ·

I am sorry, forgot to add additional files to new project. I have updated reference. Please check it.


0 Votes 0 ·