diff --git a/UserNotifications/iOS/CustomizedExpandedModifiedRemoteNotification.png b/UserNotifications/iOS/CustomizedExpandedModifiedRemoteNotification.png new file mode 100644 index 0000000..5948143 Binary files /dev/null and b/UserNotifications/iOS/CustomizedExpandedModifiedRemoteNotification.png differ diff --git a/UserNotifications/iOS/CustomizedExpandedRemoteNotification.png b/UserNotifications/iOS/CustomizedExpandedRemoteNotification.png new file mode 100644 index 0000000..0b68efe Binary files /dev/null and b/UserNotifications/iOS/CustomizedExpandedRemoteNotification.png differ diff --git a/UserNotifications/iOS/LocalNotification.png b/UserNotifications/iOS/LocalNotification.png new file mode 100644 index 0000000..1ff56b6 Binary files /dev/null and b/UserNotifications/iOS/LocalNotification.png differ diff --git a/UserNotifications/iOS/NotificationContentExtension/Entitlements.plist b/UserNotifications/iOS/NotificationContentExtension/Entitlements.plist new file mode 100644 index 0000000..9ae5993 --- /dev/null +++ b/UserNotifications/iOS/NotificationContentExtension/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/UserNotifications/iOS/NotificationContentExtension/Info.plist b/UserNotifications/iOS/NotificationContentExtension/Info.plist new file mode 100644 index 0000000..4ed4c52 --- /dev/null +++ b/UserNotifications/iOS/NotificationContentExtension/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDisplayName + NotificationContent + CFBundleName + NotificationTest.NotificationContent + CFBundleDevelopmentRegion + en + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + NSExtension + + NSExtensionAttributes + + UNNotificationExtensionCategory + general + UNNotificationExtensionInitialContentSizeRatio + 0.5 + UNNotificationExtensionDefaultContentHidden + + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.usernotifications.content-extension + + + diff --git a/UserNotifications/iOS/NotificationContentExtension/MainInterface.storyboard b/UserNotifications/iOS/NotificationContentExtension/MainInterface.storyboard new file mode 100644 index 0000000..ab87282 --- /dev/null +++ b/UserNotifications/iOS/NotificationContentExtension/MainInterface.storyboard @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UserNotifications/iOS/NotificationContentExtension/NotificationContentExtension.csproj b/UserNotifications/iOS/NotificationContentExtension/NotificationContentExtension.csproj new file mode 100644 index 0000000..a6da7c7 --- /dev/null +++ b/UserNotifications/iOS/NotificationContentExtension/NotificationContentExtension.csproj @@ -0,0 +1,16 @@ + + + net9.0-ios + Library + enable + enable + 15.0 + com.rolfkvinge.usernotifications.notificationcontentextension + True + User Notifications Content Extension Profile + + + + + + diff --git a/UserNotifications/iOS/NotificationContentExtension/NotificationViewController.cs b/UserNotifications/iOS/NotificationContentExtension/NotificationViewController.cs new file mode 100644 index 0000000..731fc76 --- /dev/null +++ b/UserNotifications/iOS/NotificationContentExtension/NotificationViewController.cs @@ -0,0 +1,58 @@ +using System; + +using Foundation; +using ObjCRuntime; +using UIKit; +using UserNotifications; +using UserNotificationsUI; + +namespace NotificationContentExtension +{ + [Register ("NotificationViewController")] + public class NotificationViewController : UIViewController, IUNNotificationContentExtension + { + const int LabelHeight = 88; + UILabel? notificationLabel; + + protected NotificationViewController (NativeHandle handle) : base (handle) + { + // Note: this .ctor should not contain any initialization logic. + // it only exists so that the OS can instantiate an instance of this class. + } + + public override void ViewDidLoad () + { + base.ViewDidLoad (); + + Console.WriteLine ($"UserNotifications.NotificationContentExtension.NotificationViewController.ViewDidLoad ()"); + + View.BackgroundColor = UIColor.Clear; + + notificationLabel = new UILabel () + { + TranslatesAutoresizingMaskIntoConstraints = false, + Lines = 0, + TextAlignment = UITextAlignment.Center, + }; + View.AddSubview (notificationLabel); + + notificationLabel.TopAnchor.ConstraintEqualTo (View.TopAnchor).Active = true; + notificationLabel.LeadingAnchor.ConstraintEqualTo (View.LeadingAnchor).Active = true; + notificationLabel.TrailingAnchor.ConstraintEqualTo (View.TrailingAnchor).Active = true; + notificationLabel.HeightAnchor.ConstraintEqualTo (LabelHeight).Active = true; + } + + void IUNNotificationContentExtension.DidReceiveNotification(UNNotification notification) + { + Console.WriteLine ($"UserNotifications.NotificationContentExtension.NotificationViewController.DidReceiveNotification ({notification})"); + + if (notificationLabel is not null) { + notificationLabel.Text = + $"➡️ {notification.Request.Content.Title}\n" + + $"↪️ {notification.Request.Content.Subtitle}\n" + + $"⏩ {notification.Request.Content.Body}"; + } + } + } +} + diff --git a/UserNotifications/iOS/NotificationServiceExtension/Entitlements.plist b/UserNotifications/iOS/NotificationServiceExtension/Entitlements.plist new file mode 100644 index 0000000..9ae5993 --- /dev/null +++ b/UserNotifications/iOS/NotificationServiceExtension/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/UserNotifications/iOS/NotificationServiceExtension/Info.plist b/UserNotifications/iOS/NotificationServiceExtension/Info.plist new file mode 100644 index 0000000..9c6a8de --- /dev/null +++ b/UserNotifications/iOS/NotificationServiceExtension/Info.plist @@ -0,0 +1,27 @@ + + + + + CFBundleDisplayName + NotificationService + CFBundleName + NotificationTest.NotificationService + CFBundleDevelopmentRegion + en + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + NotificationService + + + diff --git a/UserNotifications/iOS/NotificationServiceExtension/NotificationService.cs b/UserNotifications/iOS/NotificationServiceExtension/NotificationService.cs new file mode 100644 index 0000000..bb1a103 --- /dev/null +++ b/UserNotifications/iOS/NotificationServiceExtension/NotificationService.cs @@ -0,0 +1,37 @@ +using System; + +using Foundation; +using ObjCRuntime; +using UIKit; +using UserNotifications; + +namespace NotificationServiceExtension +{ + [Register ("NotificationService")] // this must match the value of the 'NSExtensionPrincipalClass' key in the extension's Info.plist + public class NotificationService : UNNotificationServiceExtension + { + protected NotificationService (NativeHandle handle) : base (handle) + { + // Note: this .ctor should not contain any initialization logic. + } + + public override void DidReceiveNotificationRequest(UNNotificationRequest request, Action contentHandler) + { + Console.WriteLine ($"UserNotifications.NotificationServiceExtension.DidReceiveNotificationRequest ({request})"); + + var newContent = (UNMutableNotificationContent) request.Content.MutableCopy (); + + // Modify the notification content here... + newContent.Title = $"[modified] {newContent.Title}"; + + contentHandler (newContent); + } + + 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. + } + } +} + diff --git a/UserNotifications/iOS/NotificationServiceExtension/NotificationServiceExtension.csproj b/UserNotifications/iOS/NotificationServiceExtension/NotificationServiceExtension.csproj new file mode 100644 index 0000000..40612f1 --- /dev/null +++ b/UserNotifications/iOS/NotificationServiceExtension/NotificationServiceExtension.csproj @@ -0,0 +1,16 @@ + + + net9.0-ios + Library + enable + enable + 15.0 + com.rolfkvinge.usernotifications.notificationserviceextension + True + User Notifications Service Extension Profile + + + + + + diff --git a/UserNotifications/iOS/README.md b/UserNotifications/iOS/README.md new file mode 100644 index 0000000..91e79fa --- /dev/null +++ b/UserNotifications/iOS/README.md @@ -0,0 +1,117 @@ +--- +name: User notifications in iOS +description: "Demonstrates how to use user notifications in a .NET for iOS app" +page_type: sample +languages: +- csharp +products: +- dotnet-macios +urlFragment: usernotifications-ios +--- + +# User Notifications in iOS + +User Notifications is a sample app that accompanies the article [User notifications](https://learn.microsoft.com/en-us/dotnet/ios/app-fundamentals/user-notifications/user-notifications). + +It demonstrates how to delivery and handling of user notifications at runtime. + +Note that running this example project locally requires creating new app +identifiers and provisioning profiles for both the main project and each app +extension project, and then updating the bundle identifier (the +`ApplicationId` in the project file) for each project according to the chosen +app identifier. + +See the [User Notifications](https://learn.microsoft.com/en-us/dotnet/ios/app-fundamentals/user-notifications/user-notifications) article for more information. + +## Local notifications + +Clicking on the `Send Local Notification` button sends a local notification: + +[![Local notification received](LocalNotification.png)](LocalNotification.png) + +## Remote notification + +Sending a remote notification (for instance using Apple's Push Notification Console): + +[![Remote notification received](RemoteNotification.png)](RemoteNotification.png) + +Example payload: + +```json +{ + "aps": { + "alert": { + "title": "Hello From Apple's Push Notification Console", + "subtitle": "Subtitle", + "body": "Body" } + } +} +``` + +## Modified remote notification using a Notification Service extension: + +Sending a remote notification (for instance using Apple's Push Notification +Console), with the `mutable-content` key in the payload set to `1` makes the +iOS device deliver the notification to any Notification Service extensions, +which can modify the payload: + +[![Modified remote notification received](ModifiedRemoteNotification.png)](ModifiedRemoteNotification.png) + +Example payload: + +```json +{ + "aps": { + "alert": { + "title": "Hello From Apple's Push Notification Console", + "subtitle": "Subtitle", + "body": "Body" }, + "mutable-content": 1 + } +} +``` + + +## Modified remote notification using a Notification Service extension: + +If the user long-presses a notification, iOS can display an extended interface +for the notification if the app contains a Content Service extension: + +[![Customized expanded remote notification](CustomizedExpandedRemoteNotification.png)](CustomizedExpandedRemoteNotification.png) + +This requires the `category` key in the payload to be set to a category listed in the `UNNotificationExtensionCategory` entry in the Notification Service extension's `Info.plist`. + +Example payload: + +```json +{ + "aps": { + "alert": { + "title": "Hello From Apple's Push Notification Console", + "subtitle": "Subtitle", + "body": "Body" }, + "category": "general" + } +} +``` + +## Both extensions at the same time + +Both a Notification Service extension and a Content Service extension can process a notification: + +[![Customized expanded modified remote notification](CustomizedExpandedModifiedRemoteNotification.png)](CustomizedExpandedModifiedRemoteNotification.png) + +Example payload: + +```json +{ + "aps": { + "alert": { + "title": "Hello From Apple's Push Notification Console", + "subtitle": "Subtitle", + "body": "Body" }, + "category": "general", + "mutable-content": 1 + } +} +``` diff --git a/UserNotifications/iOS/RemoteNotification.png b/UserNotifications/iOS/RemoteNotification.png new file mode 100644 index 0000000..9cfdd53 Binary files /dev/null and b/UserNotifications/iOS/RemoteNotification.png differ diff --git a/UserNotifications/iOS/RemoteNotificationModified.png b/UserNotifications/iOS/RemoteNotificationModified.png new file mode 100644 index 0000000..0ede6b3 Binary files /dev/null and b/UserNotifications/iOS/RemoteNotificationModified.png differ diff --git a/UserNotifications/iOS/UserNotifications/AppDelegate.cs b/UserNotifications/iOS/UserNotifications/AppDelegate.cs new file mode 100644 index 0000000..46e2bb8 --- /dev/null +++ b/UserNotifications/iOS/UserNotifications/AppDelegate.cs @@ -0,0 +1,80 @@ +using UserNotifications; + +namespace NotificationTest; + +[Register ("AppDelegate")] +public class AppDelegate : UIApplicationDelegate, IUNUserNotificationCenterDelegate { + public override UIWindow? Window { + get; + set; + } + + public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) + { + UNUserNotificationCenter.Current.Delegate = this; + + UNNotificationAction dismissAction = + UNNotificationAction.FromIdentifier("dismiss", "Dismiss", UNNotificationActionOptions.Destructive); + + UNNotificationCategory watchCategory = UNNotificationCategory.FromIdentifier("dismiss", + new[] { dismissAction }, [], "", UNNotificationCategoryOptions.None); + + NSSet categories = new NSSet(watchCategory); + UNUserNotificationCenter.Current.SetNotificationCategories(categories); + + var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound | UNAuthorizationOptions.Badge; + UNUserNotificationCenter.Current.RequestAuthorization(authOptions, (granted, error) => + { + System.Diagnostics.Debug.WriteLine($"Notification permission granted: {granted} (error: {error})"); + if (granted) + UNUserNotificationCenter.Current.Delegate = new NotificationReceiver (); + }); + UIApplication.SharedApplication.RegisterForRemoteNotifications(); + + Window = new UIWindow (UIScreen.MainScreen.Bounds); + Window.RootViewController = new RootViewController(); + Window.MakeKeyAndVisible(); + return true; + } + + public void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action completionHandler) + { + completionHandler(UNNotificationPresentationOptions.List | UNNotificationPresentationOptions.Banner | + UNNotificationPresentationOptions.Sound); + } + + public override void RegisteredForRemoteNotifications(UIKit.UIApplication application, NSData deviceToken) + { + Console.WriteLine($"UserNotifications.AppDelegate.RegisteredForRemoteNotifications() Device Token: {deviceToken.DebugDescription}"); + Console.WriteLine($"UserNotifications.AppDelegate.RegisteredForRemoteNotifications() Device Token Hex: {string.Join ("", deviceToken.ToArray ().Select (v => $"{v:x2}"))}"); + } + + public override void FailedToRegisterForRemoteNotifications(UIKit.UIApplication application, NSError error) + { + Console.WriteLine ($"UserNotifications.AppDelegate.FailedToRegisterForRemoteNotifications(): {error}"); + } + + public override void DidReceiveRemoteNotification (UIApplication application, NSDictionary userInfo, Action completionHandler) + { + Console.WriteLine ($"UserNotifications.AppDelegate.DidReceiveRemoteNotification ({userInfo})"); + completionHandler (UIBackgroundFetchResult.NewData); + } +} + +public class NotificationReceiver : UNUserNotificationCenterDelegate +{ + // Called if app is in the foreground. + public override void WillPresentNotification (UNUserNotificationCenter center, UNNotification notification, Action completionHandler) + { + var presentationOptions = UNNotificationPresentationOptions.Banner; + Console.WriteLine ($"UserNotifications.NotificationReceiver.WillPresentNotification ({notification}) => {presentationOptions}"); + completionHandler (presentationOptions); + } + + // Called if app is in the background, or killed state. + public override void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler) + { + Console.WriteLine ($"UserNotifications.NotificationReceiver.DidReceiveNotificationResponse ({response})"); + completionHandler(); + } +} \ No newline at end of file diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Contents.json b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..98f4d03 --- /dev/null +++ b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,117 @@ +{ + "images": [ + { + "scale": "2x", + "size": "20x20", + "idiom": "iphone", + "filename": "Icon40.png" + }, + { + "scale": "3x", + "size": "20x20", + "idiom": "iphone", + "filename": "Icon60.png" + }, + { + "scale": "2x", + "size": "29x29", + "idiom": "iphone", + "filename": "Icon58.png" + }, + { + "scale": "3x", + "size": "29x29", + "idiom": "iphone", + "filename": "Icon87.png" + }, + { + "scale": "2x", + "size": "40x40", + "idiom": "iphone", + "filename": "Icon80.png" + }, + { + "scale": "3x", + "size": "40x40", + "idiom": "iphone", + "filename": "Icon120.png" + }, + { + "scale": "2x", + "size": "60x60", + "idiom": "iphone", + "filename": "Icon120.png" + }, + { + "scale": "3x", + "size": "60x60", + "idiom": "iphone", + "filename": "Icon180.png" + }, + { + "scale": "1x", + "size": "20x20", + "idiom": "ipad", + "filename": "Icon20.png" + }, + { + "scale": "2x", + "size": "20x20", + "idiom": "ipad", + "filename": "Icon40.png" + }, + { + "scale": "1x", + "size": "29x29", + "idiom": "ipad", + "filename": "Icon29.png" + }, + { + "scale": "2x", + "size": "29x29", + "idiom": "ipad", + "filename": "Icon58.png" + }, + { + "scale": "1x", + "size": "40x40", + "idiom": "ipad", + "filename": "Icon40.png" + }, + { + "scale": "2x", + "size": "40x40", + "idiom": "ipad", + "filename": "Icon80.png" + }, + { + "scale": "1x", + "size": "76x76", + "idiom": "ipad", + "filename": "Icon76.png" + }, + { + "scale": "2x", + "size": "76x76", + "idiom": "ipad", + "filename": "Icon152.png" + }, + { + "scale": "2x", + "size": "83.5x83.5", + "idiom": "ipad", + "filename": "Icon167.png" + }, + { + "scale": "1x", + "size": "1024x1024", + "idiom": "ios-marketing", + "filename": "Icon1024.png" + } + ], + "properties": {}, + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon1024.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon1024.png new file mode 100644 index 0000000..b573205 Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon1024.png differ diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon120.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon120.png new file mode 100644 index 0000000..0b74155 Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon120.png differ diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon152.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon152.png new file mode 100644 index 0000000..1c19313 Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon152.png differ diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon167.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon167.png new file mode 100644 index 0000000..4e3e8bd Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon167.png differ diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon180.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon180.png new file mode 100644 index 0000000..40a7371 Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon180.png differ diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon20.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon20.png new file mode 100644 index 0000000..8bb1383 Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon20.png differ diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon29.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon29.png new file mode 100644 index 0000000..bdd130c Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon29.png differ diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon40.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon40.png new file mode 100644 index 0000000..75d2789 Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon40.png differ diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon58.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon58.png new file mode 100644 index 0000000..06afa60 Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon58.png differ diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon60.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon60.png new file mode 100644 index 0000000..2e0db2a Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon60.png differ diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon76.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon76.png new file mode 100644 index 0000000..755bc88 Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon76.png differ diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon80.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon80.png new file mode 100644 index 0000000..6559bb4 Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon80.png differ diff --git a/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon87.png b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon87.png new file mode 100644 index 0000000..ca28c8d Binary files /dev/null and b/UserNotifications/iOS/UserNotifications/Assets.xcassets/AppIcon.appiconset/Icon87.png differ diff --git a/UserNotifications/iOS/UserNotifications/Entitlements.plist b/UserNotifications/iOS/UserNotifications/Entitlements.plist new file mode 100644 index 0000000..36a8706 --- /dev/null +++ b/UserNotifications/iOS/UserNotifications/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/UserNotifications/iOS/UserNotifications/Info.plist b/UserNotifications/iOS/UserNotifications/Info.plist new file mode 100644 index 0000000..63d7162 --- /dev/null +++ b/UserNotifications/iOS/UserNotifications/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDisplayName + notificationTest + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UIDeviceFamily + + 1 + 2 + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/UserNotifications/iOS/UserNotifications/LaunchScreen.storyboard b/UserNotifications/iOS/UserNotifications/LaunchScreen.storyboard new file mode 100644 index 0000000..2296c6b --- /dev/null +++ b/UserNotifications/iOS/UserNotifications/LaunchScreen.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UserNotifications/iOS/UserNotifications/Main.cs b/UserNotifications/iOS/UserNotifications/Main.cs new file mode 100644 index 0000000..5c08649 --- /dev/null +++ b/UserNotifications/iOS/UserNotifications/Main.cs @@ -0,0 +1,6 @@ +using NotificationTest; + +// This is the main entry point of the application. +// If you want to use a different Application Delegate class from "AppDelegate" +// you can specify it here. +UIApplication.Main (args, null, typeof (AppDelegate)); diff --git a/UserNotifications/iOS/UserNotifications/Resources/LaunchScreen.xib b/UserNotifications/iOS/UserNotifications/Resources/LaunchScreen.xib new file mode 100644 index 0000000..8190201 --- /dev/null +++ b/UserNotifications/iOS/UserNotifications/Resources/LaunchScreen.xib @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UserNotifications/iOS/UserNotifications/RootViewController.cs b/UserNotifications/iOS/UserNotifications/RootViewController.cs new file mode 100644 index 0000000..0b0d8f8 --- /dev/null +++ b/UserNotifications/iOS/UserNotifications/RootViewController.cs @@ -0,0 +1,44 @@ +using UserNotifications; + +namespace NotificationTest; + +[Register ("RootViewController")] +public class RootViewController : UIViewController +{ + public override void ViewDidLoad () + { + base.ViewDidLoad (); + + if (View == null) + return; + + View.BackgroundColor = UIColor.Black; + + UIButton button = new UIButton (UIButtonType.Custom) + { + TranslatesAutoresizingMaskIntoConstraints = false, + BackgroundColor = UIColor.Purple + }; + button.SetTitle ("Send Local Notification", UIControlState.Normal); + button.SetTitleColor (UIColor.White, UIControlState.Normal); + button.TouchUpInside += (sender, e) => + { + var content = new UNMutableNotificationContent () + { + Title = "Hi there", + Body = "Have a nice day", + Sound = UNNotificationSound.Default, + CategoryIdentifier = "general" + }; + var trigger = UNTimeIntervalNotificationTrigger.CreateTrigger (0.1f, false); + var request = UNNotificationRequest.FromIdentifier ("notificationTest", content, trigger); + UNUserNotificationCenter.Current.AddNotificationRequest (request, null); + }; + View.AddSubview (button); + + button.CenterYAnchor.ConstraintEqualTo (View.CenterYAnchor).Active = true; + button.LeadingAnchor.ConstraintEqualTo (View.LeadingAnchor).Active = true; + button.TrailingAnchor.ConstraintEqualTo (View.TrailingAnchor).Active = true; + button.HeightAnchor.ConstraintEqualTo (50).Active = true; + } +} diff --git a/UserNotifications/iOS/UserNotifications/SceneDelegate.cs b/UserNotifications/iOS/UserNotifications/SceneDelegate.cs new file mode 100644 index 0000000..39fb70c --- /dev/null +++ b/UserNotifications/iOS/UserNotifications/SceneDelegate.cs @@ -0,0 +1,54 @@ +namespace NotificationTest; + +[Register ("SceneDelegate")] +public class SceneDelegate : UIResponder, IUIWindowSceneDelegate { + + [Export ("window")] + public UIWindow? Window { get; set; } + + [Export ("scene:willConnectToSession:options:")] + public void WillConnect (UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions) + { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see UIApplicationDelegate `GetConfiguration` instead). + } + + [Export ("sceneDidDisconnect:")] + public void DidDisconnect (UIScene scene) + { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see UIApplicationDelegate `DidDiscardSceneSessions` instead). + } + + [Export ("sceneDidBecomeActive:")] + public void DidBecomeActive (UIScene scene) + { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + [Export ("sceneWillResignActive:")] + public void WillResignActive (UIScene scene) + { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + [Export ("sceneWillEnterForeground:")] + public void WillEnterForeground (UIScene scene) + { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + [Export ("sceneDidEnterBackground:")] + public void DidEnterBackground (UIScene scene) + { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } +} diff --git a/UserNotifications/iOS/UserNotifications/UserNotifications.csproj b/UserNotifications/iOS/UserNotifications/UserNotifications.csproj new file mode 100644 index 0000000..97e1ed3 --- /dev/null +++ b/UserNotifications/iOS/UserNotifications/UserNotifications.csproj @@ -0,0 +1,23 @@ + + + net9.0-ios + Exe + enable + enable + 15.0 + com.rolfkvinge.usernotifications + User Notifications Profile + + + + + + + + true + + + true + + +