如何为iOS版本9 + 10(或者8)实现苹果推送通知?

时间:2022-09-02 11:16:12

I have not found any official Apple document that discusses correctly implementing push notification simultaneously for old iOS versions, as well as iOS 10. And the independent tutorials I have seen, likewise cover a single iOS version.

我还没有找到任何官方的苹果文档来讨论如何正确地对旧的iOS版本和iOS 10进行推送通知。我所看到的独立教程,同样也包含了一个iOS版本。

I see this official document for iOS 10: Local and Remote Notifications Overview But it doesn't comment on supporting earlier iOS versions.

我看到了iOS 10的官方文件:本地和远程通知概述,但它不评论支持早期的iOS版本。

And a tutorial for iOS 9: Push Notifications Tutorial - Ray Wenderlich

还有一个针对iOS 9的教程:推送通知教程——Ray Wenderlich。

And I see various * threads about changes people had to make to get their old solutions to work on newer versions:

我看到了各种各样的*线程,关于改变人们必须要让他们的旧的解决方案工作在更新的版本:

Push notifications are not working in iOS 9 Which does show code for handling 6 - 9.

在ios9中,推送通知不工作,它显示了处理6 - 9的代码。

didReceiveRemoteNotification not called , iOS 10

没有调用didReceiveRemoteNotification, ios10。


BUT what I don't see, is what is correct to do, starting today (with iOS 10), but also supporting older devices. ** UPDATE ** App Store says only 6% of devices downloading apps are older than ios 9, so if it is easier to just support 9 + 10, i'll just do that.

但我不明白的是,从今天开始(iOS 10),正确的做法是,但也支持旧设备。** App Store说,只有6%的下载应用的设备比ios 9更老,所以如果更容易支持9 + 10,我就这么做。

(I tried starting with an iOS 10 example, but it immediately crashed on iOS 9.3 emulated device, though it works fine in iOS 10. So I conclude that I should be starting with information about correctly setting up for the different versions. I could post that code, but I think that leads this thread in the wrong direction. I would rather start with what "should" work on multiple versions of iOS, including 10.)

(我试过以iOS 10为例,但它很快就在ios9.3仿真设备上崩溃了,尽管它在ios10中运行良好。所以我的结论是,我应该从正确设置不同版本的信息开始。我可以发布这段代码,但我认为这导致了错误的方向。我宁愿从“应该”在多个版本的iOS中开始工作,包括10个版本。

If I don't find some solution, I'll start combining code from different * code snippets ... but seriously? I must be missing something, as presumably every iOS developer has this issue.

如果我没有找到解决方案,我将从不同的*代码片段开始组合代码……但认真吗?我一定是遗漏了什么,大概每个iOS开发者都有这个问题。


Conversely, I could start with an older example, and then follow the changes to get that to work with iOS 10 - but will that take full advantage of iOS 10?

相反地,我可以从一个更老的例子开始,然后按照这些变化来运行iOS 10——但是这将充分利用iOS 10吗?

NOTE: I'm programming in Xamarin C#, but Objective-C or Swift answer is just as useful.

注意:我正在用Xamarin c#编程,但是Objective-C或Swift的回答同样有用。

1 个解决方案

#1


3  

This is Xamarin C# code (different syntax and capitalization than Objective-C, but I think it is translatable line-by-line to Objective-C).

这是Xamarin c#代码(不同于Objective-C的语法和大写,但我认为它是可翻译的逐行到Objective-C)。

Tested on both iOS 9.3 and iOS 10.2.

在iOS 9.3和ios10.2上都进行了测试。

To initialize "local" and "remote" notifications:

初始化“本地”和“远程”通知:

// "UIApplicationDelegate" is for "local" notifications,
// "IUNUserNotificationCenterDelegate, IMessagingDelegate" for "remote" notifications.
public class AppDelegate : UIApplicationDelegate,
    IUNUserNotificationCenterDelegate, IMessagingDelegate
{
    ...
    public override bool FinishedLaunching( UIApplication application, NSDictionary launchOptions )
    {
        ...
        RegisterForOurRemoteNotifications( this );
        RegisterForOurLocalNotifications();
        ...
    }
    ...

    // --- Comment out if not using Google FCM. ---
    public override void RegisteredForRemoteNotifications( UIApplication application, NSData deviceToken )
    {
        //base.RegisteredForRemoteNotifications( application, deviceToken );
        Firebase.InstanceID.InstanceId.SharedInstance.SetApnsToken( deviceToken,
                                                                   Firebase.InstanceID.ApnsTokenType.Sandbox );
    }

    ...
    // ----- "static"; Could be in another class. -----

    // These flags are for our convenience, so we know initialization was done.
    static bool IsRegisteredForNotifications;
    static bool IsRegisteredForRemoteNotifications;
    // Optional - true when we are using Google "Firebase Cloud Messaging".
    static bool HasFCM;

    public static void RegisterForOurRemoteNotifications( AppDelegate del )
    {
        // Google "Firebase Cloud Messaging" (FCM) Monitor token generation
        // (Uncomment, if you are using FCM for notifications.)
        //InstanceId.Notifications.ObserveTokenRefresh( TokenRefreshNotification );

        if (UIDevice.CurrentDevice.CheckSystemVersion( 10, 0 )) {
            // iOS 10 or later
            var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
            UNUserNotificationCenter.Current.RequestAuthorization( authOptions, ( granted, error ) => {
                Console.WriteLine( granted );
            } );

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

            // For iOS 10 data message (sent via Google FCM).
            // (Uncomment, if you are using FCM for notifications.)
            // TBD: If NOT using FCM, you may need some other lines of code here.
            //Messaging.SharedInstance.RemoteMessageDelegate = del;

        } else {
            // iOS 9 or before
            var allNotificationTypes = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound;
            var settings = UIUserNotificationSettings.GetSettingsForTypes( allNotificationTypes, null );
            UIApplication.SharedApplication.RegisterUserNotificationSettings( settings );
        }

        UIApplication.SharedApplication.RegisterForRemoteNotifications();
        IsRegisteredForRemoteNotifications = true;

        // Uncomment if using Google "Firebase Cloud Messaging" (FCM).
        //TokenRefreshNotification( null, null );
        //if (UIDevice.CurrentDevice.CheckSystemVersion( 9, 0 )) // Needed to call this twice on iOS 9 for some reason.
        //  TokenRefreshNotification( null, null );


        UIApplication.SharedApplication.SetMinimumBackgroundFetchInterval( UIApplication.BackgroundFetchIntervalMinimum );
    }

    public static void RegisterForOurLocalNotifications()
    {            
        // --- Our app's notification actions. ---
        UNNotificationAction followAction = UNNotificationAction.FromIdentifier( "follow", PS.LocalizedString( "Follow" ), UNNotificationActionOptions.None );
        UNNotificationAction likeAction = UNNotificationAction.FromIdentifier( "like", PS.LocalizedString( "Like" ), UNNotificationActionOptions.None );
        // ...

        // --- Our app's notification categories ---
        UNNotificationCategory followCategory = UNNotificationCategory.FromIdentifier( "followCategory", new UNNotificationAction[] { followAction, likeAction },
                                                                                new string[] { }, UNNotificationCategoryOptions.None );
        // ...

        // --- All of the app's categories from above ---
        var categories = new UNNotificationCategory[] { followCategory /*, ...*/ };


        // --- Same for all apps ---
        UIUserNotificationSettings settings = UIUserNotificationSettings.GetSettingsForTypes(
                                                  UIUserNotificationType.Alert |
                                                  UIUserNotificationType.Badge |
                                                  UIUserNotificationType.Sound
            , new NSSet( categories ) );
        UIApplication.SharedApplication.RegisterUserNotificationSettings( settings );


        if (UIDevice.CurrentDevice.CheckSystemVersion( 10, 0 )) {
            UNUserNotificationCenter.Current.SetNotificationCategories( new NSSet<UNNotificationCategory>( categories ) );

            UNUserNotificationCenter.Current.RequestAuthorization( UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound | UNAuthorizationOptions.Badge,
                                                                  ( result, err ) => {
                                                                      Console.WriteLine( result.ToString() );
                                                                  } );
        }

        IsRegisteredForNotifications = true;
    }
}


    // -------------------------------------------------------
    // --- These are for Google "Firebase Cloud Messaging" ---
    // (Comment out if not using FCM.)

    public static string Token;

    static void TokenRefreshNotification( object sender, NSNotificationEventArgs e )
    {
        // This method will be fired every time a new token is generated, including the first
        // time. So if you need to retrieve the token as soon as it is available this is where that
        // should be done.
        //var refreshedToken = InstanceId.SharedInstance.Token;

        ConnectToFCM( UIApplication.SharedApplication.KeyWindow.RootViewController );

        // TODO: If necessary send token to application server.
    }


    public static void ConnectToFCM( UIViewController fromViewController )
    {
        Messaging.SharedInstance.Connect( error => {
            if (error != null) {
                Helper.logD( "Unable to connect to FCM", error.LocalizedDescription );
            } else {
                //var options = new NSDictionary();
                //options.SetValueForKey( DeviceToken, Constants.RegisterAPNSOption );
                //options.SetValueForKey( new NSNumber( true ), Constants.APNSServerTypeSandboxOption );

                //InstanceId.SharedInstance.GetToken("", InstanceId.ScopeFirebaseMessaging 
                Token = InstanceId.SharedInstance.Token;

                Console.WriteLine( $"Token: {InstanceId.SharedInstance.Token}" );
                HasFCM = true;
            }
        } );
    }
    // ------------------ End Google FCM ---------------------
    // -------------------------------------------------------
}

The above code initializes your app so that it can receive notifications.

上面的代码初始化您的应用程序,以便它能够接收通知。

IMPORTANT: You also need to set appropriate permissions on your app; see Apple docs, or links mentioned in the question. And you need this file:

重要:您还需要设置应用程序的适当权限;看看苹果文档,或者问题中提到的链接。你需要这个文件:

Entitlements.plist:

Entitlements.plist:

<?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>aps-environment</key>
    <string>development</string>
</dict>
</plist>

<string> above must contain either "development" or "production". (I don't know the significance of our app still saying "development" here; I haven't examined what is built to see if it gets automatically changed to "production" by Xcode before submission to Apple. According to https://*.com/a/40857877/199364 it does.)

必须包含“开发”或“生产”。(我不知道我们app的重要性还在这里说“开发”;我还没有检查构建的内容,看它是否会在提交给Apple之前被Xcode自动更改为“生产”。根据https://*.com/a/40857877/199364所做的。


Then you need code to send [e.g. your app tells your server to notify your friends' devices of what you are doing now] and receive a local or remote notification. That code, in our app, is combined with our specific notification actions and categories; I do not have time to extract a concise version to post here. Please see Apple docs, or links mentioned in the original question, for full details.

然后需要发送代码。你的应用程序告诉你的服务器将你正在做的事情通知你的朋友的设备,并接收一个本地或远程通知。在我们的应用程序中,该代码与我们的特定通知操作和类别相结合;我没有时间在这里提取一个简洁的版本。请参阅苹果文档,或在原始问题中提到的链接,以获得详细信息。

Here are the essential methods (Add to class AppDelegate above) to receive notifications:

以下是接收通知的基本方法(添加到上面的类AppDelegate):

    public override void ReceivedLocalNotification( UIApplication application, UILocalNotification notification )
    {
        ...
    }

    public override void DidReceiveRemoteNotification( UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler )
    {
        ...
    }


    [Export( "userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:" )]
    public void DidReceiveNotificationResponse( UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler )
    {
        ...
    }

Other methods that you may want/need to override or implement (also see the interfaces declared on class AppDelegate above); some of these might be specific to FCM:

您可能想要/需要重写或实现的其他方法(也可以看到在上面的类AppDelegate上声明的接口);其中一些可能是针对FCM的:

ApplicationReceivedRemoteMessage
ReceivedRemoteNotification
WillPresentNotification
PerformFetch (for background notifications)
HandleAction

#1


3  

This is Xamarin C# code (different syntax and capitalization than Objective-C, but I think it is translatable line-by-line to Objective-C).

这是Xamarin c#代码(不同于Objective-C的语法和大写,但我认为它是可翻译的逐行到Objective-C)。

Tested on both iOS 9.3 and iOS 10.2.

在iOS 9.3和ios10.2上都进行了测试。

To initialize "local" and "remote" notifications:

初始化“本地”和“远程”通知:

// "UIApplicationDelegate" is for "local" notifications,
// "IUNUserNotificationCenterDelegate, IMessagingDelegate" for "remote" notifications.
public class AppDelegate : UIApplicationDelegate,
    IUNUserNotificationCenterDelegate, IMessagingDelegate
{
    ...
    public override bool FinishedLaunching( UIApplication application, NSDictionary launchOptions )
    {
        ...
        RegisterForOurRemoteNotifications( this );
        RegisterForOurLocalNotifications();
        ...
    }
    ...

    // --- Comment out if not using Google FCM. ---
    public override void RegisteredForRemoteNotifications( UIApplication application, NSData deviceToken )
    {
        //base.RegisteredForRemoteNotifications( application, deviceToken );
        Firebase.InstanceID.InstanceId.SharedInstance.SetApnsToken( deviceToken,
                                                                   Firebase.InstanceID.ApnsTokenType.Sandbox );
    }

    ...
    // ----- "static"; Could be in another class. -----

    // These flags are for our convenience, so we know initialization was done.
    static bool IsRegisteredForNotifications;
    static bool IsRegisteredForRemoteNotifications;
    // Optional - true when we are using Google "Firebase Cloud Messaging".
    static bool HasFCM;

    public static void RegisterForOurRemoteNotifications( AppDelegate del )
    {
        // Google "Firebase Cloud Messaging" (FCM) Monitor token generation
        // (Uncomment, if you are using FCM for notifications.)
        //InstanceId.Notifications.ObserveTokenRefresh( TokenRefreshNotification );

        if (UIDevice.CurrentDevice.CheckSystemVersion( 10, 0 )) {
            // iOS 10 or later
            var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
            UNUserNotificationCenter.Current.RequestAuthorization( authOptions, ( granted, error ) => {
                Console.WriteLine( granted );
            } );

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

            // For iOS 10 data message (sent via Google FCM).
            // (Uncomment, if you are using FCM for notifications.)
            // TBD: If NOT using FCM, you may need some other lines of code here.
            //Messaging.SharedInstance.RemoteMessageDelegate = del;

        } else {
            // iOS 9 or before
            var allNotificationTypes = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound;
            var settings = UIUserNotificationSettings.GetSettingsForTypes( allNotificationTypes, null );
            UIApplication.SharedApplication.RegisterUserNotificationSettings( settings );
        }

        UIApplication.SharedApplication.RegisterForRemoteNotifications();
        IsRegisteredForRemoteNotifications = true;

        // Uncomment if using Google "Firebase Cloud Messaging" (FCM).
        //TokenRefreshNotification( null, null );
        //if (UIDevice.CurrentDevice.CheckSystemVersion( 9, 0 )) // Needed to call this twice on iOS 9 for some reason.
        //  TokenRefreshNotification( null, null );


        UIApplication.SharedApplication.SetMinimumBackgroundFetchInterval( UIApplication.BackgroundFetchIntervalMinimum );
    }

    public static void RegisterForOurLocalNotifications()
    {            
        // --- Our app's notification actions. ---
        UNNotificationAction followAction = UNNotificationAction.FromIdentifier( "follow", PS.LocalizedString( "Follow" ), UNNotificationActionOptions.None );
        UNNotificationAction likeAction = UNNotificationAction.FromIdentifier( "like", PS.LocalizedString( "Like" ), UNNotificationActionOptions.None );
        // ...

        // --- Our app's notification categories ---
        UNNotificationCategory followCategory = UNNotificationCategory.FromIdentifier( "followCategory", new UNNotificationAction[] { followAction, likeAction },
                                                                                new string[] { }, UNNotificationCategoryOptions.None );
        // ...

        // --- All of the app's categories from above ---
        var categories = new UNNotificationCategory[] { followCategory /*, ...*/ };


        // --- Same for all apps ---
        UIUserNotificationSettings settings = UIUserNotificationSettings.GetSettingsForTypes(
                                                  UIUserNotificationType.Alert |
                                                  UIUserNotificationType.Badge |
                                                  UIUserNotificationType.Sound
            , new NSSet( categories ) );
        UIApplication.SharedApplication.RegisterUserNotificationSettings( settings );


        if (UIDevice.CurrentDevice.CheckSystemVersion( 10, 0 )) {
            UNUserNotificationCenter.Current.SetNotificationCategories( new NSSet<UNNotificationCategory>( categories ) );

            UNUserNotificationCenter.Current.RequestAuthorization( UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound | UNAuthorizationOptions.Badge,
                                                                  ( result, err ) => {
                                                                      Console.WriteLine( result.ToString() );
                                                                  } );
        }

        IsRegisteredForNotifications = true;
    }
}


    // -------------------------------------------------------
    // --- These are for Google "Firebase Cloud Messaging" ---
    // (Comment out if not using FCM.)

    public static string Token;

    static void TokenRefreshNotification( object sender, NSNotificationEventArgs e )
    {
        // This method will be fired every time a new token is generated, including the first
        // time. So if you need to retrieve the token as soon as it is available this is where that
        // should be done.
        //var refreshedToken = InstanceId.SharedInstance.Token;

        ConnectToFCM( UIApplication.SharedApplication.KeyWindow.RootViewController );

        // TODO: If necessary send token to application server.
    }


    public static void ConnectToFCM( UIViewController fromViewController )
    {
        Messaging.SharedInstance.Connect( error => {
            if (error != null) {
                Helper.logD( "Unable to connect to FCM", error.LocalizedDescription );
            } else {
                //var options = new NSDictionary();
                //options.SetValueForKey( DeviceToken, Constants.RegisterAPNSOption );
                //options.SetValueForKey( new NSNumber( true ), Constants.APNSServerTypeSandboxOption );

                //InstanceId.SharedInstance.GetToken("", InstanceId.ScopeFirebaseMessaging 
                Token = InstanceId.SharedInstance.Token;

                Console.WriteLine( $"Token: {InstanceId.SharedInstance.Token}" );
                HasFCM = true;
            }
        } );
    }
    // ------------------ End Google FCM ---------------------
    // -------------------------------------------------------
}

The above code initializes your app so that it can receive notifications.

上面的代码初始化您的应用程序,以便它能够接收通知。

IMPORTANT: You also need to set appropriate permissions on your app; see Apple docs, or links mentioned in the question. And you need this file:

重要:您还需要设置应用程序的适当权限;看看苹果文档,或者问题中提到的链接。你需要这个文件:

Entitlements.plist:

Entitlements.plist:

<?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>aps-environment</key>
    <string>development</string>
</dict>
</plist>

<string> above must contain either "development" or "production". (I don't know the significance of our app still saying "development" here; I haven't examined what is built to see if it gets automatically changed to "production" by Xcode before submission to Apple. According to https://*.com/a/40857877/199364 it does.)

必须包含“开发”或“生产”。(我不知道我们app的重要性还在这里说“开发”;我还没有检查构建的内容,看它是否会在提交给Apple之前被Xcode自动更改为“生产”。根据https://*.com/a/40857877/199364所做的。


Then you need code to send [e.g. your app tells your server to notify your friends' devices of what you are doing now] and receive a local or remote notification. That code, in our app, is combined with our specific notification actions and categories; I do not have time to extract a concise version to post here. Please see Apple docs, or links mentioned in the original question, for full details.

然后需要发送代码。你的应用程序告诉你的服务器将你正在做的事情通知你的朋友的设备,并接收一个本地或远程通知。在我们的应用程序中,该代码与我们的特定通知操作和类别相结合;我没有时间在这里提取一个简洁的版本。请参阅苹果文档,或在原始问题中提到的链接,以获得详细信息。

Here are the essential methods (Add to class AppDelegate above) to receive notifications:

以下是接收通知的基本方法(添加到上面的类AppDelegate):

    public override void ReceivedLocalNotification( UIApplication application, UILocalNotification notification )
    {
        ...
    }

    public override void DidReceiveRemoteNotification( UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler )
    {
        ...
    }


    [Export( "userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:" )]
    public void DidReceiveNotificationResponse( UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler )
    {
        ...
    }

Other methods that you may want/need to override or implement (also see the interfaces declared on class AppDelegate above); some of these might be specific to FCM:

您可能想要/需要重写或实现的其他方法(也可以看到在上面的类AppDelegate上声明的接口);其中一些可能是针对FCM的:

ApplicationReceivedRemoteMessage
ReceivedRemoteNotification
WillPresentNotification
PerformFetch (for background notifications)
HandleAction