iOS

To start with the iOS configuration, make sure you have OK these points:

App Delegate

The AppDelegate class should look like this:

[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
	protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();

    [Export("application:didFinishLaunchingWithOptions:")]
    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
    {
        // see the documentation below for details.
        var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
        UNUserNotificationCenter.Current.RequestAuthorization(authOptions, (granted, error) =>
        {
            if (granted)
            {
                InvokeOnMainThread(() =>
                {
                    UIApplication.SharedApplication.RegisterForRemoteNotifications();
                });
            }
        });

        // For iOS 10 display notification (sent via APNS)
        UNUserNotificationCenter.Current.Delegate = new UserNotificationCenterDelegate();

        // Com.Indigitall.Maui.Platforms.iOS.MIndigitall.AskPushPermission();


        return base.FinishedLaunching(application, launchOptions);

    }

    [Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")]
    public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
    {
        //Com.Indigitall.Maui.Platforms.iOS.MIndigitall.PerformFetchWithCompletionHandler(completionHandler);

    }

    [Export("application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:")]
    public void HandleAction(UIApplication application, string actionIdentifier, NSDictionary remoteNotificationInfo, NSDictionary responseInfo, Action completionHandler)
    {
        Com.Indigitall.Maui.Platforms.iOS.MIndigitall.HandleActionPush(remoteNotificationInfo, actionIdentifier, (push, action) =>
        {
            Console.WriteLine("HandleAction push: " + push);
            Console.WriteLine(" HandleAction action: " + action.App);
        });
    }

    [Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
    public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
    {
        Com.Indigitall.Maui.Platforms.iOS.MIndigitall.SetDeviceToken(deviceToken, (device) =>
        {
            Console.WriteLine("NewUserRegistered: " + device.PushToken);
        });
    }

    [Export("application:didFailToRegisterForRemoteNotificationsWithError:")]
    public void FailedToRegisterForRemoteNotifications(UIKit.UIApplication application, NSError error)
    {
        Console.WriteLine("Error token: " + error.LocalizedDescription);
    }
}


Understanding Push Notification Setup in iOS

In iOS, handling remote notifications involves two separate processes: registering the device with APNs to obtain a push token, and requesting visual permission from the user.

var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
UNUserNotificationCenter.Current.RequestAuthorization(authOptions, (granted, error) =>
{
    if (granted)
    {
        InvokeOnMainThread(() =>
        {
            UIApplication.SharedApplication.RegisterForRemoteNotifications();
        });
    }
});

Why Is This Block Needed?

  1. Explicit Permission Prompt (RequestAuthorization).
    • iOS strictly requires explicit user consent before displaying push notifications on the device (visual alerts, app badge updates, or sound alerts). Calling UNUserNotificationCenter.Current.RequestAuthorization triggers the native system prompt asking the user to allow these alerts.
  1. Triggering APNs Token Registration (RegisterForRemoteNotifications)
    • Calling UIApplication.SharedApplication.RegisterForRemoteNotifications() notifies the operating system to establish a connection with Apple Push Notification service (APNs). Once successful, iOS invokes the RegisteredForRemoteNotifications callback with the unique NSData deviceToken.
  2. Ensuring Main-Thread Execution
    • The completion handler of RequestAuthorization runs on a background thread. Since RegisterForRemoteNotifications() interacts directly with the UIApplication instance, it must be dispatched back to the UI thread using InvokeOnMainThread, preventing system crashes or unexpected behavior on iOS.

⚠️ Implementation Note: Token Generation vs. User Permission

  • In the provided snippet, RegisterForRemoteNotifications() is wrapped inside if (granted).Current Snippet Behavior: APNs registration will only trigger if the user taps "Allow". If the user denies permission, no APNs token will be generated, and RegisteredForRemoteNotifications will not fire.
  • Alternative Approach (Always Generate Token): If your application relies on silent background notifications or requires the device token regardless of permission status, move UIApplication.SharedApplication.RegisterForRemoteNotifications() outside of the RequestAuthorization callback.

UNUserNotificationCenterDelegate

You have to add the class that extends of UNUserNotificationCenterDelegate as Microsoft said in this link https://docs.microsoft.com/es-es/xamarin/ios/platform/user-notifications/enhanced-user-notifications?tabs=macos#handling-foreground-app-notifications


{
    public class YourUserNotificationCenterDelegate : UNUserNotificationCenterDelegate
    {
        #region Constructors
        public UserNotificationCenterDelegate()
        {
        }
        #endregion

        #region Override Methods
        public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
        {
            Com.Indigitall.Maui.Push.Platforms.iOS.MIndigitallPush.WillPresentNotification(completionHandler);
        }

        public override void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
        {
             IndigitallPushMaui.PushIndigitall.HandleWithResponse(response, (push, action) =>
            {
                Console.WriteLine("DidReceiveNotificationResponse push: " + push);
                Console.WriteLine("DidReceiveNotificationResponse action: " + action.App);
            });
            
        }

        #endregion
    }
}

Notification Service Extension

Since the release of iOS 10, apps can manage rich push notifications, that is, with images, gif, video, buttons, etc.
In order to use these features, your app needs to implement the Notification Service Extension.

    1. Add a new Notification Service Extension project to your solution
    1. Add the dependency Com.Indigitall.Maui from NuGet
    1. Reference the target extension in your iOS project
    1. Once you've created the extension, a new file is created within the project. It's the NotificationService. Replace its content with the following lines:
using System;
using Foundation;
using UserNotifications;

namespace Notification
{
    [Register("NotificationService")]
    public class NotificationService : UNNotificationServiceExtension
    {
        Action<UNNotificationContent> ContentHandler { get; set; }
        UNMutableNotificationContent BestAttemptContent { get; set; }
        UNNotificationRequest _request { get; set; }

        protected NotificationService(IntPtr handle) : base(handle)
        {
            // Note: this .ctor should not contain any initialization logic.
        }

        public override void DidReceiveNotificationRequest(UNNotificationRequest request, Action<UNNotificationContent> contentHandler)
        {
            ContentHandler = contentHandler;
            BestAttemptContent = (UNMutableNotificationContent)request.Content.MutableCopy();
            _request = request;

            // Modify the notification content here...
            Com.Indigitall.Maui.Platforms.iOS.MIndigitall.DidReceiveNotificationRequest(_request, ContentHandler);
        }

        public override void TimeWillExpire()
        {
            // Called just before the extension will be terminated by the system.
            // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
            if (ContentHandler != null && BestAttemptContent != null)
            {
                Com.Indigitall.Maui.Platforms.iOS.MIndigitall.TimeWillExpire(BestAttemptContent, ContentHandler);
            }

        }
    }
}