Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added UserNotifications/iOS/LocalNotification.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>
36 changes: 36 additions & 0 deletions UserNotifications/iOS/NotificationContentExtension/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>NotificationContent</string>
<key>CFBundleName</key>
<string>NotificationTest.NotificationContent</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>UNNotificationExtensionCategory</key>
<string>general</string>
<key>UNNotificationExtensionInitialContentSizeRatio</key>
<real>0.5</real>
<key>UNNotificationExtensionDefaultContentHidden</key>
<true/>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.content-extension</string>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="M4Y-Lb-cyx">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Notification View Controller-->
<scene sceneID="cwh-vc-ff4">
<objects>
<viewController id="M4Y-Lb-cyx" userLabel="Notification View Controller" customClass="NotificationViewController" customModuleProvider="" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ft6-oW-KC0"/>
<viewControllerLayoutGuide type="bottom" id="FKl-LY-JtV"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" simulatedAppContext="notificationCenter" id="S3S-Oj-5AN">
<rect key="frame" x="0.0" y="0.0" width="320" height="37"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
</subviews>
<color key="backgroundColor" red="0.45882353186607361" green="0.74901962280273438" blue="0.66666668653488159" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="FKl-LY-JtV" firstAttribute="top" secondItem="GcN-lo-r42" secondAttribute="bottom" constant="8" symbolic="YES" id="0Q0-KW-PJ6"/>
<constraint firstItem="GcN-lo-r42" firstAttribute="leading" secondItem="S3S-Oj-5AN" secondAttribute="leading" constant="20" symbolic="YES" id="6Vq-gs-PHe"/>
<constraint firstAttribute="trailing" secondItem="GcN-lo-r42" secondAttribute="trailing" constant="20" symbolic="YES" id="L8K-9R-egU"/>
<constraint firstItem="GcN-lo-r42" firstAttribute="top" secondItem="Ft6-oW-KC0" secondAttribute="bottom" constant="8" symbolic="YES" id="mYS-Cv-VNx"/>
</constraints>
</view>
<extendedEdge key="edgesForExtendedLayout"/>
<nil key="simulatedStatusBarMetrics"/>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<size key="freeformSize" width="320" height="37"/>
<connections>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="vXp-U4-Rya" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0-ios</TargetFramework>
<OutputType>Library</OutputType>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<SupportedOSPlatformVersion>15.0</SupportedOSPlatformVersion>
<ApplicationId>com.rolfkvinge.usernotifications.notificationcontentextension</ApplicationId>
<IsAppExtension>True</IsAppExtension>
<CodesignProvision>User Notifications Content Extension Profile</CodesignProvision>
</PropertyGroup>
<ItemGroup>
<CustomEntitlements Include="aps-environment" Type="String" Value="development" Condition="'$(Configuration)' != 'Release'" />
<CustomEntitlements Include="aps-environment" Type="String" Value="production" Condition="'$(Configuration)' == 'Release'" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -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}";
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>
27 changes: 27 additions & 0 deletions UserNotifications/iOS/NotificationServiceExtension/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>NotificationService</string>
<key>CFBundleName</key>
<string>NotificationTest.NotificationService</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>NotificationService</string>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -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<UNNotificationContent> 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.
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0-ios</TargetFramework>
<OutputType>Library</OutputType>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<SupportedOSPlatformVersion>15.0</SupportedOSPlatformVersion>
<ApplicationId>com.rolfkvinge.usernotifications.notificationserviceextension</ApplicationId>
<IsAppExtension>True</IsAppExtension>
<CodesignProvision>User Notifications Service Extension Profile</CodesignProvision>
</PropertyGroup>
<ItemGroup>
<CustomEntitlements Include="aps-environment" Type="String" Value="development" Condition="'$(Configuration)' != 'Release'" />
<CustomEntitlements Include="aps-environment" Type="String" Value="production" Condition="'$(Configuration)' == 'Release'" />
</ItemGroup>
</Project>
117 changes: 117 additions & 0 deletions UserNotifications/iOS/README.md
Original file line number Diff line number Diff line change
@@ -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
}
}
```
Binary file added UserNotifications/iOS/RemoteNotification.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading