CallKit in Xamarin.iOS

Die neue CallKit-API in iOS 10 ermöglicht die Integration von VoIP-Apps in die iPhone-Benutzeroberfläche und bietet Endbenutzer*innen eine vertraute Oberfläche und Erfahrung. Mit dieser API können Benutzer*innen VoIP-Anrufe über den Sperrbildschirm des iOS-Geräts anzeigen und mit ihnen interagieren und Kontakte über die Ansichten Favoriten und Kontakte der Telefon-App verwalten.

Informationen zu CallKit

Laut Apple ist CallKit ein neues Framework, das Voice Over IP (VOIP)-Apps von Drittanbietern auf eine 1. Party-Erfahrung unter iOS 10 erhöht. Mit der CallKit-API können VOIP-Apps in die i Telefon-Benutzeroberfläche integriert werden und dem Endbenutzer eine vertraute Benutzeroberfläche und Erfahrung bieten. Genau wie die integrierte Telefon-App kann ein Benutzer VOIP-Anrufe über den Sperrbildschirm des iOS-Geräts anzeigen und mit diesen interagieren und Kontakte mithilfe der Ansichten "Favoriten" und "Zuletzt verwendet" der Telefon App verwalten.

Darüber hinaus bietet die CallKit-API die Möglichkeit, App-Erweiterungen zu erstellen, die eine Telefonnummer einem Namen (Anrufer-ID) zuordnen oder dem System mitteilen können, wann eine Nummer blockiert werden soll (Anrufblockierung).

Die vorhandene VOIP-App-Oberfläche

Bevor Sie die neue CallKit-API und ihre Fähigkeiten besprechen, sehen Sie sich die aktuelle Benutzererfahrung mit einer VOIP-App von Drittanbietern in iOS 9 (und weniger) mithilfe einer fiktiven VOIP-App namens MonkeyCall an. MonkeyCall ist eine einfache App, mit der der Benutzer VOIP-Anrufe mithilfe der vorhandenen iOS-APIs senden und empfangen kann.

Wenn der Benutzer einen eingehenden Anruf auf MonkeyCall empfängt und sein i Telefon gesperrt ist, ist die empfangene Benachrichtigung auf dem Sperrbildschirm von allen anderen Arten von Benachrichtigungen (z. B. von den Nachrichten- oder Mail-Apps) nicht zu unterscheiden.

Wenn der Benutzer den Anruf annehmen wollte, müsste er die MonkeyCall-Benachrichtigung ziehen, um die App zu öffnen und seine Kennung (oder benutzer touch ID) einzugeben, um das Telefon zu entsperren, bevor er den Anruf annehmen und die Unterhaltung starten konnte.

Die Erfahrung ist ebenso mühsam, wenn das Telefon entsperrt ist. Auch hier wird der eingehende MonkeyCall-Anruf als Standardmäßiges Benachrichtigungsbanner angezeigt, das vom oberen Bildschirmrand aus eingeblendet wird. Da die Benachrichtigung temporär ist, kann sie leicht übersehen werden, indem der Benutzer sie zwingt, entweder das Benachrichtigungscenter zu öffnen und die spezifische Benachrichtigung zu finden, um dann anzurufen oder die MonkeyCall-App manuell zu suchen und zu starten.

Die CallKit VOIP-App-Oberfläche

Durch die Implementierung der neuen CallKit-APIs in der MonkeyCall-App kann die Benutzererfahrung mit einem eingehenden VOIP-Anruf in iOS 10 erheblich verbessert werden. Nehmen Sie sich das Beispiel an, dass der Benutzer einen VOIP-Anruf empfängt, wenn sein Telefon von oben gesperrt ist. Durch die Implementierung von CallKit wird der Anruf auf dem Sperrbildschirm des i Telefon angezeigt, genau wie wenn der Anruf von der integrierten Telefon-App empfangen wurde, mit der Vollbild-, systemeigenen Benutzeroberfläche und standardmäßiger Wisch-zu-Antwort-Funktionalität.

Wenn das i Telefon entsperrt ist, wenn ein MonkeyCall-VOIP-Anruf empfangen wird, wird derselbe Vollbild-, native UI- und standardmäßige Wisch-zu-Antwort- und Tap-to-Decline-Funktionalität der integrierten Telefon-App angezeigt, und MonkeyCall hat die Möglichkeit, einen benutzerdefinierten Klingelton wiederzugeben.

CallKit bietet zusätzliche Funktionen für MonkeyCall, sodass seine VOIP-Anrufe mit anderen Arten von Anrufen interagieren können, in den integrierten Listen "Zuletzt verwendet" und "Favoriten" angezeigt werden, um die integrierten Funktionen "Nicht stören" und "Blockieren" zu verwenden, MonkeyCall-Anrufe von Siri zu starten und Benutzern in der Kontakte-App MonkeyCall-Anrufe zuzuweisen.

In den folgenden Abschnitten werden die CallKit-Architektur, die Eingehenden und ausgehenden Anrufflüsse und die CallKit-API ausführlich behandelt.

Die CallKit-Architektur

In iOS 10 hat Apple CallKit in allen Systemdiensten übernommen, sodass Aufrufe an CarPlay beispielsweise über CallKit bekannt sind. Im folgenden Beispiel, da MonkeyCall CallKit verwendet, ist es für das System auf die gleiche Weise bekannt wie diese integrierten Systemdienste und ruft alle gleichen Features ab:

CallKit-Dienststapel

Sehen Sie sich die MonkeyCall-App aus dem obigen Diagramm genauer an. Die App enthält den gesamten Code für die Kommunikation mit einem eigenen Netzwerk und enthält eigene Benutzeroberflächen. Es verknüpft in CallKit mit dem System zu kommunizieren:

MonkeyCall-App-Architektur

In CallKit gibt es zwei Standard Schnittstellen, die von der App verwendet werden:

  • CXProvider – Auf diese Weise kann die MonkeyCall-App das System über alle Out-of-Band-Benachrichtigungen informieren, die auftreten können.
  • CXCallController – Ermöglicht der MonkeyCall-App, das System über lokale Benutzeraktionen zu informieren.

Der CXProvider

Wie oben erwähnt, ermöglicht es einer App, CXProvider das System über out-of-Band-Benachrichtigungen zu informieren, die auftreten können. Dies sind Benachrichtigungen, die aufgrund lokaler Benutzeraktionen nicht auftreten, aber aufgrund externer Ereignisse wie eingehender Anrufe auftreten.

Eine App sollte folgendes CXProvider verwenden:

  • Melden Sie einen eingehenden Anruf an das System.
  • Melden Sie einen ausgehenden Anruf, der mit dem System verbunden ist.
  • Melden Sie dem Remotebenutzer, der den Aufruf des Systems beendet hat.

Wenn die App mit dem System kommunizieren möchte, verwendet sie die CXCallUpdate Klasse und wenn das System mit der App kommunizieren muss, verwendet sie die CXAction Klasse:

Kommunikation mit dem System über einen CXProvider

The CXCallController

Die CXCallController App ermöglicht es einer App, das System über lokale Benutzeraktionen zu informieren, z. B. den Benutzer, der einen VOIP-Anruf startet. Durch die Implementierung einer CXCallController App wechselt die Interaktion mit anderen Arten von Aufrufen im System. Wenn beispielsweise bereits ein aktiver Telefonieanruf ausgeführt wird, kann die VOIP-App zulassen, CXCallController dass dieser Anruf gehalten wird, und einen VOIP-Anruf starten oder annehmen.

Eine App sollte folgendes CXCallController verwenden:

  • Melden Sie, wenn der Benutzer einen ausgehenden Anruf an das System gestartet hat.
  • Melden Sie, wenn der Benutzer einen eingehenden Anruf an das System antwortt.
  • Melden Sie, wenn der Benutzer einen Aufruf des Systems beendet.

Wenn die App lokale Benutzeraktionen mit dem System kommunizieren möchte, verwendet sie die CXTransaction Klasse:

Melden an das System mit einem CXCallController

Implementieren von CallKit

In den folgenden Abschnitten wird gezeigt, wie CallKit in einer Xamarin.iOS VOIP-App implementiert wird. Aus Gründen des Beispiels verwendet dieses Dokument Code aus der fiktiven MonkeyCall VOIP-App. Der hier dargestellte Code stellt mehrere unterstützende Klassen dar, die callKit-spezifischen Teile werden in den folgenden Abschnitten ausführlich behandelt.

Die ActiveCall-Klasse

Die ActiveCall Klasse wird von der MonkeyCall-App verwendet, um alle Informationen zu einem VOIP-Anruf zu speichern, der derzeit wie folgt aktiv ist:

using System;
using CoreFoundation;
using Foundation;

namespace MonkeyCall
{
    public class ActiveCall
    {
        #region Private Variables
        private bool isConnecting;
        private bool isConnected;
        private bool isOnhold;
        #endregion

        #region Computed Properties
        public NSUuid UUID { get; set; }
        public bool isOutgoing { get; set; }
        public string Handle { get; set; }
        public DateTime StartedConnectingOn { get; set;}
        public DateTime ConnectedOn { get; set;}
        public DateTime EndedOn { get; set; }

        public bool IsConnecting {
            get { return isConnecting; }
            set {
                isConnecting = value;
                if (isConnecting) StartedConnectingOn = DateTime.Now;
                RaiseStartingConnectionChanged ();
            }
        }

        public bool IsConnected {
            get { return isConnected; }
            set {
                isConnected = value;
                if (isConnected) {
                    ConnectedOn = DateTime.Now;
                } else {
                    EndedOn = DateTime.Now;
                }
                RaiseConnectedChanged ();
            }
        }

        public bool IsOnHold {
            get { return isOnhold; }
            set {
                isOnhold = value;
            }
        }
        #endregion

        #region Constructors
        public ActiveCall ()
        {
        }

        public ActiveCall (NSUuid uuid, string handle, bool outgoing)
        {
            // Initialize
            this.UUID = uuid;
            this.Handle = handle;
            this.isOutgoing = outgoing;
        }
        #endregion

        #region Public Methods
        public void StartCall (ActiveCallbackDelegate completionHandler)
        {
            // Simulate the call starting successfully
            completionHandler (true);

            // Simulate making a starting and completing a connection
            DispatchQueue.MainQueue.DispatchAfter (new DispatchTime(DispatchTime.Now, 3000), () => {
                // Note that the call is starting
                IsConnecting = true;

                // Simulate pause before connecting
                DispatchQueue.MainQueue.DispatchAfter (new DispatchTime (DispatchTime.Now, 1500), () => {
                    // Note that the call has connected
                    IsConnecting = false;
                    IsConnected = true;
                });
            });
        }

        public void AnswerCall (ActiveCallbackDelegate completionHandler)
        {
            // Simulate the call being answered
            IsConnected = true;
            completionHandler (true);
        }

        public void EndCall (ActiveCallbackDelegate completionHandler)
        {
            // Simulate the call ending
            IsConnected = false;
            completionHandler (true);
        }
        #endregion

        #region Events
        public delegate void ActiveCallbackDelegate (bool successful);
        public delegate void ActiveCallStateChangedDelegate (ActiveCall call);

        public event ActiveCallStateChangedDelegate StartingConnectionChanged;
        internal void RaiseStartingConnectionChanged ()
        {
            if (this.StartingConnectionChanged != null) this.StartingConnectionChanged (this);
        }

        public event ActiveCallStateChangedDelegate ConnectedChanged;
        internal void RaiseConnectedChanged ()
        {
            if (this.ConnectedChanged != null) this.ConnectedChanged (this);
        }
        #endregion
    }
}

ActiveCall enthält mehrere Eigenschaften, die den Status des Aufrufs und zwei Ereignisse definieren, die ausgelöst werden können, wenn sich der Aufrufstatus ändert. Da dies nur ein Beispiel ist, werden drei Methoden verwendet, um einen Aufruf zu simulieren, zu beantworten und zu beenden.

Die StartCallRequest-Klasse

Die StartCallRequest statische Klasse stellt einige Hilfsmethoden bereit, die beim Arbeiten mit ausgehenden Aufrufen verwendet werden:

using System;
using Foundation;
using Intents;

namespace MonkeyCall
{
    public static class StartCallRequest
    {
        public static string URLScheme {
            get { return "monkeycall"; }
        }

        public static string ActivityType {
            get { return INIntentIdentifier.StartAudioCall.GetConstant ().ToString (); }
        }

        public static string CallHandleFromURL (NSUrl url)
        {
            // Is this a MonkeyCall handle?
            if (url.Scheme == URLScheme) {
                // Yes, return host
                return url.Host;
            } else {
                // Not handled
                return null;
            }
        }

        public static string CallHandleFromActivity (NSUserActivity activity)
        {
            // Is this a start call activity?
            if (activity.ActivityType == ActivityType) {
                // Yes, trap any errors
                try {
                    // Get first contact
                    var interaction = activity.GetInteraction ();
                    var startAudioCallIntent = interaction.Intent as INStartAudioCallIntent;
                    var contact = startAudioCallIntent.Contacts [0];

                    // Get the person handle
                    return contact.PersonHandle.Value;
                } catch {
                    // Error, report null
                    return null;
                }
            } else {
                // Not handled
                return null;
            }
        }
    }
}

Die CallHandleFromURL Klassen CallHandleFromActivity und Klassen werden in AppDelegate verwendet, um den Kontakthandle der Person abzurufen, die in einem ausgehenden Anruf aufgerufen wird. Weitere Informationen finden Sie im Abschnitt "Behandlung ausgehender Anrufe " weiter unten.

Die ActiveCallManager-Klasse

Die ActiveCallManager Klasse verarbeitet alle geöffneten Anrufe in der MonkeyCall-App.

using System;
using System.Collections.Generic;
using Foundation;
using CallKit;

namespace MonkeyCall
{
    public class ActiveCallManager
    {
        #region Private Variables
        private CXCallController CallController = new CXCallController ();
        #endregion

        #region Computed Properties
        public List<ActiveCall> Calls { get; set; }
        #endregion

        #region Constructors
        public ActiveCallManager ()
        {
            // Initialize
            this.Calls = new List<ActiveCall> ();
        }
        #endregion

        #region Private Methods
        private void SendTransactionRequest (CXTransaction transaction)
        {
            // Send request to call controller
            CallController.RequestTransaction (transaction, (error) => {
                // Was there an error?
                if (error == null) {
                    // No, report success
                    Console.WriteLine ("Transaction request sent successfully.");
                } else {
                    // Yes, report error
                    Console.WriteLine ("Error requesting transaction: {0}", error);
                }
            });
        }
        #endregion

        #region Public Methods
        public ActiveCall FindCall (NSUuid uuid)
        {
            // Scan for requested call
            foreach (ActiveCall call in Calls) {
                if (call.UUID.Equals(uuid)) return call;
            }

            // Not found
            return null;
        }

        public void StartCall (string contact)
        {
            // Build call action
            var handle = new CXHandle (CXHandleType.Generic, contact);
            var startCallAction = new CXStartCallAction (new NSUuid (), handle);

            // Create transaction
            var transaction = new CXTransaction (startCallAction);

            // Inform system of call request
            SendTransactionRequest (transaction);
        }

        public void EndCall (ActiveCall call)
        {
            // Build action
            var endCallAction = new CXEndCallAction (call.UUID);

            // Create transaction
            var transaction = new CXTransaction (endCallAction);

            // Inform system of call request
            SendTransactionRequest (transaction);
        }

        public void PlaceCallOnHold (ActiveCall call)
        {
            // Build action
            var holdCallAction = new CXSetHeldCallAction (call.UUID, true);

            // Create transaction
            var transaction = new CXTransaction (holdCallAction);

            // Inform system of call request
            SendTransactionRequest (transaction);
        }

        public void RemoveCallFromOnHold (ActiveCall call)
        {
            // Build action
            var holdCallAction = new CXSetHeldCallAction (call.UUID, false);

            // Create transaction
            var transaction = new CXTransaction (holdCallAction);

            // Inform system of call request
            SendTransactionRequest (transaction);
        }
        #endregion
    }
}

Da dies nur eine Simulation ist, enthält die ActiveCallManager einzige Standard eine Auflistung von ActiveCall Objekten und verfügt über eine Routine zum Auffinden eines bestimmten Aufrufs durch seine UUID Eigenschaft. Sie enthält auch Methoden zum Starten, Beenden und Ändern des Haltezustands eines ausgehenden Anrufs. Weitere Informationen finden Sie im Abschnitt "Behandlung ausgehender Anrufe " weiter unten.

Die ProviderDelegate-Klasse

Wie oben beschrieben, bietet eine CXProvider bidirektionale Kommunikation zwischen der App und dem System für Out-of-Band-Benachrichtigungen. Der Entwickler muss eine benutzerdefinierte CXProviderDelegate App bereitstellen und an die CXProvider App anfügen, um Out-of-Band-CallKit-Ereignisse zu behandeln. MonkeyCall verwendet Folgendes CXProviderDelegate:

using System;
using Foundation;
using CallKit;
using UIKit;

namespace MonkeyCall
{
    public class ProviderDelegate : CXProviderDelegate
    {
        #region Computed Properties
        public ActiveCallManager CallManager { get; set;}
        public CXProviderConfiguration Configuration { get; set; }
        public CXProvider Provider { get; set; }
        #endregion

        #region Constructors
        public ProviderDelegate (ActiveCallManager callManager)
        {
            // Save connection to call manager
            CallManager = callManager;

            // Define handle types
            var handleTypes = new [] { (NSNumber)(int)CXHandleType.PhoneNumber };

            // Get Image Template
            var templateImage = UIImage.FromFile ("telephone_receiver.png");

            // Setup the initial configurations
            Configuration = new CXProviderConfiguration ("MonkeyCall") {
                MaximumCallsPerCallGroup = 1,
                SupportedHandleTypes = new NSSet<NSNumber> (handleTypes),
                IconTemplateImageData = templateImage.AsPNG(),
                RingtoneSound = "musicloop01.wav"
            };

            // Create a new provider
            Provider = new CXProvider (Configuration);

            // Attach this delegate
            Provider.SetDelegate (this, null);

        }
        #endregion

        #region Override Methods
        public override void DidReset (CXProvider provider)
        {
            // Remove all calls
            CallManager.Calls.Clear ();
        }

        public override void PerformStartCallAction (CXProvider provider, CXStartCallAction action)
        {
            // Create new call record
            var activeCall = new ActiveCall (action.CallUuid, action.CallHandle.Value, true);

            // Monitor state changes
            activeCall.StartingConnectionChanged += (call) => {
                if (call.isConnecting) {
                    // Inform system that the call is starting
                    Provider.ReportConnectingOutgoingCall (call.UUID, call.StartedConnectingOn.ToNSDate());
                }
            };

            activeCall.ConnectedChanged += (call) => {
                if (call.isConnected) {
                    // Inform system that the call has connected
                    provider.ReportConnectedOutgoingCall (call.UUID, call.ConnectedOn.ToNSDate ());
                }
            };

            // Start call
            activeCall.StartCall ((successful) => {
                // Was the call able to be started?
                if (successful) {
                    // Yes, inform the system
                    action.Fulfill ();

                    // Add call to manager
                    CallManager.Calls.Add (activeCall);
                } else {
                    // No, inform system
                    action.Fail ();
                }
            });
        }

        public override void PerformAnswerCallAction (CXProvider provider, CXAnswerCallAction action)
        {
            // Find requested call
            var call = CallManager.FindCall (action.CallUuid);

            // Found?
            if (call == null) {
                // No, inform system and exit
                action.Fail ();
                return;
            }

            // Attempt to answer call
            call.AnswerCall ((successful) => {
                // Was the call successfully answered?
                if (successful) {
                    // Yes, inform system
                    action.Fulfill ();
                } else {
                    // No, inform system
                    action.Fail ();
                }
            });
        }

        public override void PerformEndCallAction (CXProvider provider, CXEndCallAction action)
        {
            // Find requested call
            var call = CallManager.FindCall (action.CallUuid);

            // Found?
            if (call == null) {
                // No, inform system and exit
                action.Fail ();
                return;
            }

            // Attempt to answer call
            call.EndCall ((successful) => {
                // Was the call successfully answered?
                if (successful) {
                    // Remove call from manager's queue
                    CallManager.Calls.Remove (call);

                    // Yes, inform system
                    action.Fulfill ();
                } else {
                    // No, inform system
                    action.Fail ();
                }
            });
        }

        public override void PerformSetHeldCallAction (CXProvider provider, CXSetHeldCallAction action)
        {
            // Find requested call
            var call = CallManager.FindCall (action.CallUuid);

            // Found?
            if (call == null) {
                // No, inform system and exit
                action.Fail ();
                return;
            }

            // Update hold status
            call.isOnHold = action.OnHold;

            // Inform system of success
            action.Fulfill ();
        }

        public override void TimedOutPerformingAction (CXProvider provider, CXAction action)
        {
            // Inform user that the action has timed out
        }

        public override void DidActivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
        {
            // Start the calls audio session here
        }

        public override void DidDeactivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
        {
            // End the calls audio session and restart any non-call
            // related audio
        }
        #endregion

        #region Public Methods
        public void ReportIncomingCall (NSUuid uuid, string handle)
        {
            // Create update to describe the incoming call and caller
            var update = new CXCallUpdate ();
            update.RemoteHandle = new CXHandle (CXHandleType.Generic, handle);

            // Report incoming call to system
            Provider.ReportNewIncomingCall (uuid, update, (error) => {
                // Was the call accepted
                if (error == null) {
                    // Yes, report to call manager
                    CallManager.Calls.Add (new ActiveCall (uuid, handle, false));
                } else {
                    // Report error to user here
                    Console.WriteLine ("Error: {0}", error);
                }
            });
        }
        #endregion
    }
}

Wenn eine Instanz dieser Stellvertretung erstellt wird, wird die Instanz übergeben, die ActiveCallManager sie für die Behandlung von Anrufaktivitäten verwendet. Als Nächstes werden die Handletypen (CXHandleType) definiert, auf die die CXProvider antwortet:

// Define handle types
var handleTypes = new [] { (NSNumber)(int)CXHandleType.PhoneNumber };

Und es ruft das Vorlagenbild ab, das auf das Symbol der App angewendet wird, wenn ein Anruf ausgeführt wird:

// Get Image Template
var templateImage = UIImage.FromFile ("telephone_receiver.png");

Diese Werte werden gebündelt in eine CXProviderConfiguration , die zum Konfigurieren der CXProvider:

// Setup the initial configurations
Configuration = new CXProviderConfiguration ("MonkeyCall") {
    MaximumCallsPerCallGroup = 1,
    SupportedHandleTypes = new NSSet<NSNumber> (handleTypes),
    IconTemplateImageData = templateImage.AsPNG(),
    RingtoneSound = "musicloop01.wav"
};

Der Delegat erstellt dann eine neue CXProvider mit diesen Konfigurationen und fügt sich selbst an:

// Create a new provider
Provider = new CXProvider (Configuration);

// Attach this delegate
Provider.SetDelegate (this, null);

Wenn Sie CallKit verwenden, erstellt und verarbeitet die App keine eigenen Audiositzungen mehr, sondern sie muss eine Audiositzung konfigurieren und verwenden, die das System dafür erstellt und verarbeitet.

Wenn dies eine echte App wäre, würde die DidActivateAudioSession Methode verwendet, um den Aufruf mit einem vorab konfigurierten AVAudioSession System zu starten:

public override void DidActivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
    // Start the call's audio session here...
}

Es würde auch die DidDeactivateAudioSession Methode verwenden, um die Verbindung mit der vom System bereitgestellten Audiositzung abzuschließen und freizugeben:

public override void DidDeactivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
    // End the calls audio session and restart any non-call
    // releated audio
}

Der Rest des Codes wird in den folgenden Abschnitten ausführlich behandelt.

Die AppDelegate-Klasse

MonkeyCall verwendet appDelegate zum Halten von Instanzen der ActiveCallManager App und CXProviderDelegate die in der gesamten App verwendet werden:

using Foundation;
using UIKit;
using Intents;
using System;

namespace MonkeyCall
{
    [Register ("AppDelegate")]
    public class AppDelegate : UIApplicationDelegate
    {
        #region Constructors
        public override UIWindow Window { get; set; }
        public ActiveCallManager CallManager { get; set; }
        public ProviderDelegate CallProviderDelegate { get; set; }
        #endregion

        #region Override Methods
        public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
        {
            // Initialize the call handlers
            CallManager = new ActiveCallManager ();
            CallProviderDelegate = new ProviderDelegate (CallManager);

            return true;
        }

        public override bool OpenUrl (UIApplication app, NSUrl url, NSDictionary options)
        {
            // Get handle from url
            var handle = StartCallRequest.CallHandleFromURL (url);

            // Found?
            if (handle == null) {
                // No, report to system
                Console.WriteLine ("Unable to get call handle from URL: {0}", url);
                return false;
            } else {
                // Yes, start call and inform system
                CallManager.StartCall (handle);
                return true;
            }
        }

        public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
        {
            var handle = StartCallRequest.CallHandleFromActivity (userActivity);

            // Found?
            if (handle == null) {
                // No, report to system
                Console.WriteLine ("Unable to get call handle from User Activity: {0}", userActivity);
                return false;
            } else {
                // Yes, start call and inform system
                CallManager.StartCall (handle);
                return true;
            }
        }

        ...
        #endregion
    }
}

Die OpenUrl Methoden und ContinueUserActivity Außerkraftsetzungsmethoden werden verwendet, wenn die App einen ausgehenden Aufruf verarbeitet. Weitere Informationen finden Sie im Abschnitt "Behandlung ausgehender Anrufe " weiter unten.

Behandeln eingehender Anrufe

Es gibt mehrere Zustände und Prozesse, die ein eingehender VOIP-Anruf während eines typischen eingehenden Anrufworkflows durchlaufen kann, z. B.:

  • Informieren des Benutzers (und des Systems), dass ein eingehender Anruf vorhanden ist.
  • Empfangen von Benachrichtigungen, wenn der Benutzer den Anruf annehmen und den Anruf mit dem anderen Benutzer initialisieren möchte.
  • Informieren Sie das System und das Kommunikationsnetzwerk, wenn der Benutzer den aktuellen Anruf beenden möchte.

In den folgenden Abschnitten wird ausführlich erläutert, wie eine App CallKit verwenden kann, um den Workflow für eingehende Anrufe zu verarbeiten. Dabei wird die MonkeyCall VOIP-App als Beispiel verwendet.

Informieren des Benutzers über eingehende Anrufe

Wenn ein Remotebenutzer eine VOIP-Unterhaltung mit dem lokalen Benutzer gestartet hat, tritt Folgendes auf:

Ein Remotebenutzer hat eine VOIP-Unterhaltung gestartet

  1. Die App erhält eine Benachrichtigung über das Kommunikationsnetzwerk, dass ein eingehender VOIP-Anruf vorhanden ist.
  2. Die App verwendet die CXProvider App, um eine CXCallUpdate an das System zu senden, das ihn über den Anruf informiert.
  3. Das System veröffentlicht den Aufruf der Systembenutzeroberfläche, der Systemdienste und aller anderen VOIP-Apps, die CallKit verwenden.

Beispiel:CXProviderDelegate

public void ReportIncomingCall (NSUuid uuid, string handle)
{
    // Create update to describe the incoming call and caller
    var update = new CXCallUpdate ();
    update.RemoteHandle = new CXHandle (CXHandleType.Generic, handle);

    // Report incoming call to system
    Provider.ReportNewIncomingCall (uuid, update, (error) => {
        // Was the call accepted
        if (error == null) {
            // Yes, report to call manager
            CallManager.Calls.Add (new ActiveCall (uuid, handle, false));
        } else {
            // Report error to user here
            Console.WriteLine ("Error: {0}", error);
        }
    });
}

Mit diesem Code wird eine neue CXCallUpdate Instanz erstellt und ein Handle angefügt, das den Aufrufer identifiziert. Als Nächstes wird die ReportNewIncomingCall Methode der CXProvider Klasse verwendet, um das System des Aufrufs zu informieren. Wenn dies erfolgreich ist, wird der Aufruf der App-Sammlung aktiver Aufrufe hinzugefügt, falls dies nicht der Grund ist, muss der Fehler dem Benutzer gemeldet werden.

Benutzer, der eingehende Anrufe entgegennehmen

Wenn der Benutzer den eingehenden VOIP-Anruf annehmen möchte, tritt Folgendes auf:

Der Benutzer antwortt auf den eingehenden VOIP-Anruf.

  1. Die Systembenutzeroberfläche informiert das System, dass der Benutzer den VOIP-Anruf annehmen möchte.
  2. Das System sendet eine CXAnswerCallAction an die App CXProvider , die sie über die Antwortabsicht informiert.
  3. Die App informiert ihr Kommunikationsnetzwerk darüber, dass der Benutzer den Anruf entgegennehmen und der VOIP-Anruf wie gewohnt fortgesetzt wird.

Beispiel:CXProviderDelegate

public override void PerformAnswerCallAction (CXProvider provider, CXAnswerCallAction action)
{
    // Find requested call
    var call = CallManager.FindCall (action.CallUuid);

    // Found?
    if (call == null) {
        // No, inform system and exit
        action.Fail ();
        return;
    }

    // Attempt to answer call
    call.AnswerCall ((successful) => {
        // Was the call successfully answered?
        if (successful) {
            // Yes, inform system
            action.Fulfill ();
        } else {
            // No, inform system
            action.Fail ();
        }
    });
}

Dieser Code sucht zuerst in der Liste der aktiven Aufrufe nach dem angegebenen Aufruf. Wenn der Aufruf nicht gefunden werden kann, wird das System benachrichtigt und die Methode beendet. Wenn sie gefunden wird, wird die AnswerCall Methode der ActiveCall Klasse aufgerufen, um den Aufruf zu starten, und das System ist Informationen, wenn sie erfolgreich ist oder fehlschlägt.

Benutzer, der eingehenden Anruf beendet

Wenn der Benutzer den Aufruf innerhalb der Benutzeroberfläche der App beenden möchte, tritt Folgendes auf:

Der Benutzer beendet den Aufruf aus der Benutzeroberfläche der App.

  1. Die App erstellt CXEndCallAction , die in einem CXTransaction Paket gebündelt wird, das an das System gesendet wird, um ihn darüber zu informieren, dass der Anruf beendet wird.
  2. Das System überprüft die Endanrufabsicht und sendet über die App zurück CXEndCallAction an die CXProviderApp.
  3. Die App informiert dann sein Kommunikationsnetzwerk darüber, dass der Anruf beendet ist.

Beispiel:CXProviderDelegate

public override void PerformEndCallAction (CXProvider provider, CXEndCallAction action)
{
    // Find requested call
    var call = CallManager.FindCall (action.CallUuid);

    // Found?
    if (call == null) {
        // No, inform system and exit
        action.Fail ();
        return;
    }

    // Attempt to answer call
    call.EndCall ((successful) => {
        // Was the call successfully answered?
        if (successful) {
            // Remove call from manager's queue
            CallManager.Calls.Remove (call);

            // Yes, inform system
            action.Fulfill ();
        } else {
            // No, inform system
            action.Fail ();
        }
    });
}

Dieser Code sucht zuerst in der Liste der aktiven Aufrufe nach dem angegebenen Aufruf. Wenn der Aufruf nicht gefunden werden kann, wird das System benachrichtigt und die Methode beendet. Wenn sie gefunden wird, wird die EndCall Methode der ActiveCall Klasse aufgerufen, um den Aufruf zu beenden, und das System ist Informationen, wenn sie erfolgreich ist oder fehlschlägt. Bei erfolgreicher Ausführung wird der Aufruf aus der Sammlung aktiver Aufrufe entfernt.

Verwalten mehrerer Anrufe

Die meisten VOIP-Apps können mehrere Anrufe gleichzeitig verarbeiten. Wenn beispielsweise derzeit ein aktiver VOIP-Anruf vorhanden ist und die App benachrichtigt, dass ein neuer eingehender Anruf vorhanden ist, kann der Benutzer beim ersten Anruf anhalten oder auflegen, um den zweiten Anruf anzunehmen.

In der obigen Situation sendet das System eine CXTransaction an die App, die eine Liste mit mehreren Aktionen enthält (z. B. die CXEndCallAction und die CXAnswerCallAction). Alle diese Aktionen müssen einzeln erfüllt werden, damit das System die Benutzeroberfläche entsprechend aktualisieren kann.

Behandeln ausgehender Anrufe

Wenn der Benutzer auf einen Eintrag aus der Liste "Zuletzt verwendet" (in der Telefon-App) tippt, z. B. von einem Anruf, der zur App gehört, wird vom System eine Startanrufabsicht gesendet:

Empfangen einer Startanrufabsicht

  1. Die App erstellt eine Startanrufaktion basierend auf der Startanrufabsicht, die sie vom System erhalten hat.
  2. Die App verwendet die CXCallController App, um die Startanrufaktion vom System anzufordern.
  3. Wenn das System die Aktion akzeptiert, wird sie über die Stellvertretung an die XCProvider App zurückgegeben.
  4. Die App startet den ausgehenden Anruf mit seinem Kommunikationsnetzwerk.

Weitere Informationen zu Intents finden Sie in der Dokumentation zu Intents- und Intents-UI-Erweiterungen .

Der Lebenszyklus des ausgehenden Anrufs

Beim Arbeiten mit CallKit und einem ausgehenden Anruf muss die App das System über die folgenden Lebenszyklusereignisse informieren:

  1. Start – Informieren Sie das System darüber, dass ein ausgehender Anruf gestartet werden soll.
  2. Gestartet – Informieren Sie das System, dass ein ausgehender Anruf gestartet wurde.
  3. Verbinden ing – Informieren Sie das System, dass der ausgehende Anruf eine Verbindung herstellt.
  4. Verbinden ed – Informieren Sie den ausgehenden Anruf, und dass beide Parteien jetzt sprechen können.

Der folgende Code startet z. B. einen ausgehenden Anruf:

private CXCallController CallController = new CXCallController ();
...

private void SendTransactionRequest (CXTransaction transaction)
{
    // Send request to call controller
    CallController.RequestTransaction (transaction, (error) => {
        // Was there an error?
        if (error == null) {
            // No, report success
            Console.WriteLine ("Transaction request sent successfully.");
        } else {
            // Yes, report error
            Console.WriteLine ("Error requesting transaction: {0}", error);
        }
    });
}

public void StartCall (string contact)
{
    // Build call action
    var handle = new CXHandle (CXHandleType.Generic, contact);
    var startCallAction = new CXStartCallAction (new NSUuid (), handle);

    // Create transaction
    var transaction = new CXTransaction (startCallAction);

    // Inform system of call request
    SendTransactionRequest (transaction);
}

Es erstellt eine CXHandle und verwendet es, um eine CXStartCallAction zu konfigurieren, die mithilfe RequestTransaction der Methode der CXCallController Klasse in ein CXTransaction System gebündelt wird, das an das System gesendet wird. Durch Aufrufen der RequestTransaction Methode kann das System alle vorhandenen Aufrufe im Halteraum platzieren, unabhängig von der Quelle (Telefon App, FaceTime, VOIP usw.), bevor der neue Aufruf gestartet wird.

Die Anforderung zum Starten eines ausgehenden VOIP-Anrufs kann aus verschiedenen Quellen stammen, z. B. Siri, ein Eintrag in einem Kontakt-Karte (in der Kontakt-App) oder aus der Liste "Zuletzt verwendet" (in der Telefon-App). In diesen Fällen wird die App eine Startanrufabsicht innerhalb einer NSUserActivity App gesendet, und die AppDelegate muss sie behandeln:

public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
    var handle = StartCallRequest.CallHandleFromActivity (userActivity);

    // Found?
    if (handle == null) {
        // No, report to system
        Console.WriteLine ("Unable to get call handle from User Activity: {0}", userActivity);
        return false;
    } else {
        // Yes, start call and inform system
        CallManager.StartCall (handle);
        return true;
    }
}

Hier wird die CallHandleFromActivity Methode der Hilfsklasse StartCallRequest verwendet, um das Handle für die aufgerufene Person abzurufen (siehe Die StartCallRequest-Klasse oben).

Die PerformStartCallAction Methode der ProviderDelegate-Klasse wird verwendet, um schließlich den tatsächlichen ausgehenden Aufruf zu starten und das System über seinen Lebenszyklus zu informieren:

public override void PerformStartCallAction (CXProvider provider, CXStartCallAction action)
{
    // Create new call record
    var activeCall = new ActiveCall (action.CallUuid, action.CallHandle.Value, true);

    // Monitor state changes
    activeCall.StartingConnectionChanged += (call) => {
        if (call.IsConnecting) {
            // Inform system that the call is starting
            Provider.ReportConnectingOutgoingCall (call.UUID, call.StartedConnectingOn.ToNSDate());
        }
    };

    activeCall.ConnectedChanged += (call) => {
        if (call.IsConnected) {
            // Inform system that the call has connected
            Provider.ReportConnectedOutgoingCall (call.UUID, call.ConnectedOn.ToNSDate ());
        }
    };

    // Start call
    activeCall.StartCall ((successful) => {
        // Was the call able to be started?
        if (successful) {
            // Yes, inform the system
            action.Fulfill ();

            // Add call to manager
            CallManager.Calls.Add (activeCall);
        } else {
            // No, inform system
            action.Fail ();
        }
    });
}

Es erstellt eine Instanz der ActiveCall Klasse (um Informationen zum laufenden Anruf zu speichern) und füllt mit der Person auf, die aufgerufen wird. Die StartingConnectionChanged Ereignisse ConnectedChanged werden verwendet, um den Lebenszyklus ausgehender Anrufe zu überwachen und zu melden. Der Aufruf wird gestartet, und das System hat informiert, dass die Aktion erfüllt wurde.

Beenden eines ausgehenden Anrufs

Wenn der Benutzer einen ausgehenden Anruf abgeschlossen hat und ihn beenden möchte, kann der folgende Code verwendet werden:

private CXCallController CallController = new CXCallController ();
...

private void SendTransactionRequest (CXTransaction transaction)
{
    // Send request to call controller
    CallController.RequestTransaction (transaction, (error) => {
        // Was there an error?
        if (error == null) {
            // No, report success
            Console.WriteLine ("Transaction request sent successfully.");
        } else {
            // Yes, report error
            Console.WriteLine ("Error requesting transaction: {0}", error);
        }
    });
}

public void EndCall (ActiveCall call)
{
    // Build action
    var endCallAction = new CXEndCallAction (call.UUID);

    // Create transaction
    var transaction = new CXTransaction (endCallAction);

    // Inform system of call request
    SendTransactionRequest (transaction);
}

Wenn eine CXEndCallAction mit der UUID des End-Aufrufs erstellt wird, bündeln Sie sie in einem CXTransaction Paket, das mithilfe RequestTransaction der Methode der CXCallController Klasse an das System gesendet wird.

Zusätzliche CallKit-Details

In diesem Abschnitt werden einige zusätzliche Details behandelt, die der Entwickler beim Arbeiten mit CallKit berücksichtigen muss, z. B.:

  • Anbieterkonfiguration
  • Aktionsfehler
  • Systemeinschränkungen
  • VOIP-Audio

Providerkonfiguration

Die Anbieterkonfiguration ermöglicht es einer iOS 10-VOIP-App, die Benutzeroberfläche (innerhalb der nativen Benutzeroberfläche für Anrufe) beim Arbeiten mit CallKit anzupassen.

Eine App kann die folgenden Arten von Anpassungen vornehmen:

  • Zeigt einen lokalisierten Namen an.
  • Aktivieren sie die Videoanrufunterstützung.
  • Passen Sie die Schaltflächen auf der Benutzeroberfläche von In-Call an, indem Sie ein eigenes Vorlagenbildsymbol darstellen. Benutzerinteraktionen mit benutzerdefinierten Schaltflächen werden direkt an die zu verarbeitende App gesendet.

Aktionsfehler

iOS 10 VOIP-Apps mit CallKit müssen Aktionen ordnungsgemäß behandeln und den Benutzer jederzeit über den Status "Aktion" informieren.

Berücksichtigen Sie das folgende Beispiel:

  1. Die App hat eine Startanrufaktion erhalten und begonnen, einen neuen VOIP-Anruf mit seinem Kommunikationsnetzwerk zu initialisieren.
  2. Aufgrund einer begrenzten oder keiner Netzwerkkommunikationsfunktion schlägt diese Verbindung fehl.
  3. Die App muss die Fehlermeldung "Fehler " zurück an die Startanrufaktion (Action.Fail()) senden, um das System über den Fehler zu informieren.
  4. Dadurch kann das System den Benutzer über den Status des Anrufs informieren. Um z. B. die Anruffehlerbenutzeroberfläche anzuzeigen.

Darüber hinaus muss eine iOS 10 VOIP-App auf Timeoutfehler reagieren, die auftreten können, wenn eine erwartete Aktion nicht innerhalb eines bestimmten Zeitraums verarbeitet werden kann. Jeder von CallKit bereitgestellte Aktionstyp weist einen maximalen Timeoutwert auf. Diese Timeoutwerte stellen sicher, dass jede vom Benutzer angeforderte CallKit-Aktion reaktionsfähig behandelt wird und somit auch das Betriebssystem flüssig und reaktionsfähig bleibt.

Es gibt mehrere Methoden für den Anbieterdelegat (CXProviderDelegate), die überschrieben werden sollten, um diese Timeoutsituationen auch ordnungsgemäß zu behandeln.

Systemeinschränkungen

Basierend auf dem aktuellen Zustand des iOS-Geräts, auf dem die iOS 10 VOIP-App ausgeführt wird, können bestimmte Systemeinschränkungen erzwungen werden.

Beispielsweise kann ein eingehender VOIP-Anruf vom System eingeschränkt werden, wenn:

  1. Die Person, die anruft, befindet sich in der Liste der blockierten Anrufer des Benutzers.
  2. Das iOS-Gerät des Benutzers befindet sich im Modus "Nicht stören".

Wenn ein VOIP-Aufruf durch eine dieser Situationen eingeschränkt ist, verwenden Sie den folgenden Code, um ihn zu behandeln:

public class ProviderDelegate : CXProviderDelegate
{
...

    public void ReportIncomingCall (NSUuid uuid, string handle)
    {
        // Create update to describe the incoming call and caller
        var update = new CXCallUpdate ();
        update.RemoteHandle = new CXHandle (CXHandleType.Generic, handle);

        // Report incoming call to system
        Provider.ReportNewIncomingCall (uuid, update, (error) => {
            // Was the call accepted
            if (error == null) {
                // Yes, report to call manager
                CallManager.Calls.Add (new ActiveCall (uuid, handle, false));
            } else {
                // Report error to user here
                if (error.Code == (int)CXErrorCodeIncomingCallError.CallUuidAlreadyExists) {
                    // Handle duplicate call ID
                } else if (error.Code == (int)CXErrorCodeIncomingCallError.FilteredByBlockList) {
                    // Handle call from blocked user
                } else if (error.Code == (int)CXErrorCodeIncomingCallError.FilteredByDoNotDisturb) {
                    // Handle call while in do-not-disturb mode
                } else {
                    // Handle unknown error
                }
            }
        });
    }

}

VOIP-Audio

CallKit bietet mehrere Vorteile für die Behandlung der Audioressourcen, die eine VoIP-App für iOS 10 während eines Live-VOIP-Anrufs benötigt. Einer der größten Vorteile besteht darin, dass die Audiositzung der App bei der Ausführung in iOS 10 erhöhte Prioritäten hat. Dies ist die gleiche Prioritätsstufe wie die integrierten Telefon- und FaceTime-Apps, und diese höhere Prioritätsstufe verhindert, dass andere ausgeführte Apps die Audiositzung der VOIP-App unterbrechen.

Darüber hinaus hat CallKit Zugriff auf andere Audioroutinghinweise, die die Leistung verbessern und VOIP-Audio während eines Liveanrufs basierend auf Benutzereinstellungen und Gerätezuständen intelligent an bestimmte Ausgabegeräte weiterleiten können. Beispielsweise basierend auf angeschlossenen Geräten wie Bluetooth-Kopfhörern, einer Live-CarPlay-Verbindung oder den Barrierefreiheitseinstellungen.

Während des Lebenszyklus eines typischen VOIP-Anrufs mit CallKit muss die App den Audiostream konfigurieren, den CallKit bereitstellt. Sehen Sie sich das folgende Beispiel an:

Die Aktionssequenz für den Startaufruf

  1. Eine Startanrufaktion wird von der App empfangen, um einen eingehenden Anruf zu beantworten.
  2. Bevor diese Aktion von der App erfüllt wird, stellt sie die Konfiguration bereit, die für sie AVAudioSessionerforderlich ist.
  3. Die App informiert das System darüber, dass die Aktion erfüllt wurde.
  4. Bevor der Anruf eine Verbindung herstellt, stellt CallKit eine hohe Priorität AVAudioSession bereit, die der von der App angeforderten Konfiguration entsprechen. Die App wird über die DidActivateAudioSession Methode der CXProviderDelegateApp benachrichtigt.

Arbeiten mit Anrufverzeichniserweiterungen

Beim Arbeiten mit CallKit bieten Anrufverzeichniserweiterungen eine Möglichkeit, blockierte Anrufnummern hinzuzufügen und Nummern zu identifizieren, die für eine bestimmte VOIP-App für Kontakte in der Kontakt-App auf dem iOS-Gerät spezifisch sind.

Implementieren einer Anrufverzeichniserweiterung

Gehen Sie wie folgt vor, um eine Anrufverzeichniserweiterung in einer Xamarin.iOS-App zu implementieren:

  1. Öffnen Sie die App-Lösung in Visual Studio für Mac.

  2. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Projektmappennamen, und wählen Sie "Neues Projekt hinzufügen">aus.

  3. Wählen Sie die Anrufverzeichniserweiterungen für iOS-Erweiterungen>>aus, und klicken Sie auf die Schaltfläche "Weiter":

    Erstellen einer neuen Anrufverzeichniserweiterung

  4. Geben Sie einen Namen für die Erweiterung ein, und klicken Sie auf die Schaltfläche "Weiter":

    Eingeben eines Namens für die Erweiterung

  5. Passen Sie den Projektnamen und/oder projektmappennamen bei Bedarf an, und klicken Sie auf die Schaltfläche "Erstellen ":

    Erstellen des Projekts

Dadurch wird dem Projekt eine CallDirectoryHandler.cs Klasse hinzugefügt, die wie folgt aussieht:

using System;

using Foundation;
using CallKit;

namespace MonkeyCallDirExtension
{
    [Register ("CallDirectoryHandler")]
    public class CallDirectoryHandler : CXCallDirectoryProvider, ICXCallDirectoryExtensionContextDelegate
    {
        #region Constructors
        protected CallDirectoryHandler (IntPtr handle) : base (handle)
        {
            // Note: this .ctor should not contain any initialization logic.
        }
        #endregion

        #region Override Methods
        public override void BeginRequest (CXCallDirectoryExtensionContext context)
        {
            context.Delegate = this;

            if (!AddBlockingPhoneNumbers (context)) {
                Console.WriteLine ("Unable to add blocking phone numbers");
                var error = new NSError (new NSString ("CallDirectoryHandler"), 1, null);
                context.CancelRequest (error);
                return;
            }

            if (!AddIdentificationPhoneNumbers (context)) {
                Console.WriteLine ("Unable to add identification phone numbers");
                var error = new NSError (new NSString ("CallDirectoryHandler"), 2, null);
                context.CancelRequest (error);
                return;
            }

            context.CompleteRequest (null);
        }
        #endregion

        #region Private Methods
        private bool AddBlockingPhoneNumbers (CXCallDirectoryExtensionContext context)
        {
            // Retrieve phone numbers to block from data store. For optimal performance and memory usage when there are many phone numbers,
            // consider only loading a subset of numbers at a given time and using autorelease pool(s) to release objects allocated during each batch of numbers which are loaded.
            //
            // Numbers must be provided in numerically ascending order.

            long [] phoneNumbers = { 14085555555, 18005555555 };

            foreach (var phoneNumber in phoneNumbers)
                context.AddBlockingEntry (phoneNumber);

            return true;
        }

        private bool AddIdentificationPhoneNumbers (CXCallDirectoryExtensionContext context)
        {
            // Retrieve phone numbers to identify and their identification labels from data store. For optimal performance and memory usage when there are many phone numbers,
            // consider only loading a subset of numbers at a given time and using autorelease pool(s) to release objects allocated during each batch of numbers which are loaded.
            //
            // Numbers must be provided in numerically ascending order.

            long [] phoneNumbers = { 18775555555, 18885555555 };
            string [] labels = { "Telemarketer", "Local business" };

            for (var i = 0; i < phoneNumbers.Length; i++) {
                long phoneNumber = phoneNumbers [i];
                string label = labels [i];
                context.AddIdentificationEntry (phoneNumber, label);
            }

            return true;
        }
        #endregion

        #region Public Methods
        public void RequestFailed (CXCallDirectoryExtensionContext extensionContext, NSError error)
        {
            // An error occurred while adding blocking or identification entries, check the NSError for details.
            // For Call Directory error codes, see the CXErrorCodeCallDirectoryManagerError enum.
            //
            // This may be used to store the error details in a location accessible by the extension's containing app, so that the
            // app may be notified about errors which occurred while loading data even if the request to load data was initiated by
            // the user in Settings instead of via the app itself.
        }
        #endregion
    }
}

Die BeginRequest Methode im Aufrufverzeichnishandler muss geändert werden, um die erforderliche Funktionalität bereitzustellen. Im Fall des obigen Beispiels wird versucht, die Liste der blockierten und verfügbaren Nummern in der Kontaktdatenbank der VOIP-App festzulegen. Wenn eine der Anforderungen aus irgendeinem Grund fehlschlägt, erstellen Sie einen NSError Fehler, um den Fehler zu beschreiben, und übergeben Sie ihn an die CancelRequest Methode der CXCallDirectoryExtensionContext Klasse.

Verwenden Sie zum Festlegen der blockierten Nummern die AddBlockingEntry Methode der CXCallDirectoryExtensionContext Klasse. Die für die Methode angegebenen Zahlen müssen in numerischer aufsteigender Reihenfolge vorliegen. Für eine optimale Leistung und Speicherauslastung, wenn viele Telefonnummern vorhanden sind, sollten Sie nur eine Teilmenge von Nummern zu einem bestimmten Zeitpunkt laden und autorelease-Pool(en) verwenden, um Objekte freizugeben, die während jeder Batch von Nummern zugeordnet sind, die geladen werden.

Verwenden Sie die AddIdentificationEntry Methode der CXCallDirectoryExtensionContext Klasse, um die Kontakt-App über die Kontaktnummern zu informieren, die der VOIP-App bekannt sind, und geben Sie sowohl die Nummer als auch eine identifizierende Bezeichnung an. Auch hier müssen die für die Methode angegebenen Zahlen in numerischer aufsteigender Reihenfolge vorliegen. Für eine optimale Leistung und Speicherauslastung, wenn viele Telefonnummern vorhanden sind, sollten Sie nur eine Teilmenge von Nummern zu einem bestimmten Zeitpunkt laden und autorelease-Pool(en) verwenden, um Objekte freizugeben, die während jeder Batch von Nummern zugeordnet sind, die geladen werden.

Zusammenfassung

In diesem Artikel wurde die neue CallKit-API behandelt, die Apple in iOS 10 veröffentlicht hat und wie sie in Xamarin.iOS VOIP-Apps implementiert wird. Es wurde gezeigt, wie CallKit es einer App ermöglicht, in das iOS-System zu integrieren, wie sie Featureparität mit integrierten Apps (z. B. Telefon) bietet und wie sie die Sichtbarkeit einer App an Orten wie der Sperr- und Startbildschirm über Siri-Interaktionen und über die Kontakte-Apps erhöht.