CallKit in Xamarin.iOS

Die neue CallKit-API in iOS 10 bietet VOIP-Apps eine Möglichkeit, sich in die iPhone-Benutzeroberfläche zu integrieren und dem Endbenutzer eine vertraute Benutzeroberfläche und Benutzeroberfläche zu bieten. Mit dieser API können Benutzer VOIP-Anrufe über den Sperrbildschirm des iOS-Geräts anzeigen und damit interagieren und Kontakte mithilfe der Ansichten "Favoriten " und "Zuletzt verwendet " der Telefon-App verwalten.

Informationen zu CallKit

Laut Apple ist CallKit ein neues Framework, das VoIP-Apps (Voice Over IP) von Drittanbietern auf eine Drittanbietererfahrung unter iOS 10 erhöht. Die CallKit-API ermöglicht die Integration von VOIP-Apps in die iPhone-Benutzeroberfläche und bietet dem Endbenutzer eine vertraute Benutzeroberfläche und Benutzeroberfläche. Genau wie die integrierte Telefon-App kann ein Benutzer VOIP-Anrufe über den Sperrbildschirm des iOS-Geräts anzeigen und damit 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

Bevor Sie die neue CallKit-API und ihre Fähigkeiten besprechen, werfen Sie einen Blick auf die aktuelle Benutzererfahrung mit einer VOIP-App eines Drittanbieters in iOS 9 (und niedriger) mit einer fiktiven VOIP-App namens MonkeyCall. MonkeyCall ist eine einfache App, die es dem Benutzer ermöglicht, VOIP-Anrufe mit den vorhandenen iOS-APIs zu senden und zu empfangen.

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

Wenn der Benutzer den Anruf annehmen möchte, muss er die MonkeyCall-Benachrichtigung schieben, um die App zu öffnen, und seine Kennung (oder touch id des Benutzers) eingeben, um das Telefon zu entsperren, bevor er den Anruf annehmen und die Unterhaltung starten konnte.

Die Erfahrung ist ebenso umständlich, wenn das Telefon entsperrt wird. Auch hier wird der eingehende MonkeyCall-Anruf als Standardbenachrichtigungsbanner angezeigt, das von oben auf dem Bildschirm eingeblendet wird. Da die Benachrichtigung vorübergehend ist, kann sie leicht übersehen werden, wenn der Benutzer ihn zwingt, entweder die Benachrichtigungszentrale zu öffnen und die spezifische Benachrichtigung zu finden, um dann zu antworten, oder die MonkeyCall-App manuell zu suchen und zu starten.

Die CallKit-VOIP-App

Durch die Implementierung der neuen CallKit-APIs in der MonkeyCall-App kann die Benutzerfreundlichkeit mit einem eingehenden VOIP-Anruf in iOS 10 erheblich verbessert werden. Nehmen wir das Beispiel für den Benutzer, der einen VOIP-Anruf empfängt, wenn sein Telefon von oben gesperrt ist. Durch die Implementierung von CallKit wird der Anruf auf dem Sperrbildschirm des iPhones angezeigt, genau wie wenn der Anruf von der integrierten Telefon-App mit der Vollbild-, nativen Benutzeroberfläche und standardmäßiger Wisch-zu-Antwort-Funktionalität empfangen würde.

Wenn das iPhone entsperrt wird, wenn ein MonkeyCall-VOIP-Anruf empfangen wird, wird die gleiche Vollbild-, native Benutzeroberfläche und standard-Wisch-zu-Antwort- und Tipp-to-Decline-Funktionalität der integrierten Telefon-App angezeigt, und MonkeyCall hat die Möglichkeit, einen benutzerdefinierten Klingelton abzuspielen.

CallKit bietet zusätzliche Funktionen für MonkeyCall, sodass seine VOIP-Aufrufe mit anderen Arten von Anrufen interagieren, in den integrierten Listen "Zuletzt verwendet" und "Favoriten" angezeigt werden, die integrierten Funktionen "Nicht stören" und "Blockieren" verwenden, MonkeyCall-Anrufe von Siri starten und Benutzern die Möglichkeit bieten, MonkeyCall-Anrufe personen in der Kontakte-App 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 eingeführt, sodass Aufrufe, die beispielsweise über CarPlay getätigt werden, der System-Ui über CallKit bekannt sind. Im folgenden Beispiel ist das System seit der Übernahme von CallKit durch MonkeyCall auf die gleiche Weise wie diese integrierten Systemdienste bekannt und erhält alle gleichen Features:

Der CallKit-Dienststapel

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

MonkeyCall-App-Architektur

Es gibt zwei Standard Schnittstellen in CallKit, die die App verwendet:

  • CXProvider – Dadurch kann die MonkeyCall-App das System über mögliche Out-of-Band-Benachrichtigungen informieren.
  • CXCallController – Ermöglicht der MonkeyCall-App, das System über lokale Benutzeraktionen zu informieren.

Der CXProvider

Wie bereits erwähnt, CXProvider kann eine App das System über alle out-of-band-Benachrichtigungen informieren, die auftreten können. Dies sind Benachrichtigungen, die nicht aufgrund von lokalen Benutzeraktionen auftreten, sondern aufgrund externer Ereignisse wie eingehender Anrufe auftreten.

Eine App sollte für CXProvider Folgendes verwenden:

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

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

Der CXCallController

CXCallController Ermöglicht es einer App, das System über lokale Benutzeraktionen zu informieren, z. B. den Benutzer, der einen VOIP-Anruf startet. Durch die Implementierung eines CXCallController wechselt die App mit anderen Arten von Aufrufen im System. Wenn beispielsweise bereits ein aktiver Telefonanruf ausgeführt wird, CXCallController kann die VOIP-App diesen Anruf in die Warteschleife versetzen und einen VOIP-Anruf starten oder annehmen.

Eine App sollte für CXCallController Folgendes verwenden:

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

Wenn die App lokale Benutzeraktionen an das System kommunizieren möchte, verwendet sie die CXTransaction -Klasse:

Berichterstellung an das System mithilfe eines CXCallControllers

Implementieren von CallKit

In den folgenden Abschnitten wird gezeigt, wie CallKit in einer Xamarin.iOS-VOIP-App implementiert wird. In diesem Dokument wird beispielsweise Code aus der fiktiven MonkeyCall-VOIP-App verwendet. Der hier vorgestellte 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 derzeit aktiven VOIP-Anruf wie folgt zu speichern:

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 Zustand des Aufrufs definieren, und zwei Ereignisse, die ausgelöst werden können, wenn sich der Aufrufzustand ändert. Da dies nur ein Beispiel ist, gibt es drei Methoden, mit denen das Starten, Annehmen und Beenden eines Anrufs simuliert wird.

Die StartCallRequest-Klasse

Die StartCallRequest statische Klasse stellt einige Hilfsmethoden bereit, die bei der Arbeit 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 und CallHandleFromActivity werden in AppDelegate verwendet, um das Kontakthandle der Person abzurufen, die in einem ausgehenden Anruf aufgerufen wird. Weitere Informationen finden Sie weiter unten im Abschnitt Behandlung ausgehender Anrufe .

Die ActiveCallManager-Klasse

Die ActiveCallManager -Klasse verarbeitet alle offenen 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, verwaltet nur eine ActiveCallManager Auflistung von ActiveCall -Objekten und verfügt über eine Routine zum Suchen eines bestimmten Aufrufs durch seine UUID -Eigenschaft. Es enthält auch Methoden zum Starten, Beenden und Ändern des Haltezustands eines ausgehenden Anrufs. Weitere Informationen finden Sie weiter unten im Abschnitt Behandlung ausgehender Anrufe .

Die ProviderDelegate-Klasse

Wie bereits erwähnt, bietet eine CXProvider bidirektionale Kommunikation zwischen der App und dem System für Out-of-Band-Benachrichtigungen. Der Entwickler muss einen benutzerdefinierten CXProviderDelegate bereitstellen und an den CXProvider anfügen, damit die App CallKit-Ereignisse aus dem Band verarbeiten kann. 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 ein instance dieses Delegats erstellt wird, wird der übergeben, den ActiveCallManager er zum Verarbeiten aller Aufrufaktivitäten verwendet. Als Nächstes werden die Handletypen (CXHandleType) definiert, auf die der 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 in einem CXProviderConfiguration gebündelt, das zum Konfigurieren von CXProviderverwendet wird:

// 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 den folgenden Konfigurationen und fügt sich selbst an diese an:

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

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

Bei Verwendung von CallKit erstellt und verarbeitet die App keine eigenen Audiositzungen mehr, sondern muss eine Audiositzung konfigurieren und verwenden, die vom System erstellt und verarbeitet wird.

Wenn dies eine echte App wäre, würde die DidActivateAudioSession -Methode verwendet, um den Aufruf mit einer vorkonfigurierten AVAudioSession , die vom System bereitgestellt wurde, zu starten:

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

Außerdem wird die -Methode verwendet, um die DidDeactivateAudioSession 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, um Instanzen von und CXProviderDelegate zu enthalten, die ActiveCallManager 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 - und ContinueUserActivity -Außerkraftsetzungsmethoden werden verwendet, wenn die App einen ausgehenden Aufruf verarbeitet. Weitere Informationen finden Sie weiter unten im Abschnitt Behandlung ausgehender Anrufe .

Behandeln eingehender Anrufe

Es gibt mehrere Zustände und Prozesse, die ein eingehender VOIP-Anruf während eines typischen Workflows für eingehende Anrufe durchlaufen kann, z. B.:

  • Den Benutzer (und das System) darüber informieren, dass ein eingehender Anruf vorhanden ist.
  • Erhalten einer Benachrichtigung, 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 wiederum die MonkeyCall-VOIP-App als Beispiel verwendet.

Benutzer über eingehende Anrufe informieren

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

Ein Remotebenutzer hat eine VOIP-Konversation gestartet.

  1. Die App erhält eine Benachrichtigung von ihrem Kommunikationsnetzwerk, dass ein eingehender VOIP-Anruf vorliegt.
  2. Die App verwendet , CXProvider um eine CXCallUpdate an das System zu senden, um sie über den Anruf zu informieren.
  3. Das System veröffentlicht den Aufruf der System-Benutzeroberfläche, der Systemdienste und aller anderen VOIP-Apps mithilfe von CallKit.

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);
        }
    });
}

Dieser Code erstellt eine neue CXCallUpdate instance und fügt ein Handle an, das den Aufrufer identifiziert. Als Nächstes wird die ReportNewIncomingCall -Methode der CXProvider -Klasse verwendet, um das System über den Aufruf zu informieren. Wenn er erfolgreich ist, wird der Aufruf zur Sammlung aktiver Aufrufe der App hinzugefügt. Wenn dies nicht der Fall ist, muss der Fehler dem Benutzer gemeldet werden.

Benutzer, der eingehende Anrufe entgegennehmen

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

Der Benutzer beantwortet den eingehenden VOIP-Anruf.

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

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 nach dem angegebenen Aufruf in der Liste der aktiven Aufrufe. Wenn der Aufruf nicht gefunden werden kann, wird das System benachrichtigt, und die Methode wird beendet. Wenn sie gefunden wird, wird die AnswerCall -Methode der ActiveCall -Klasse aufgerufen, um den Aufruf zu starten, und das System ist eine Information, wenn sie erfolgreich ist oder fehlschlägt.

Benutzer, der den eingehenden Anruf beendet

Wenn der Benutzer den Aufruf über die Benutzeroberfläche der App beenden möchte, geschieht Folgendes:

Der Benutzer beendet den Aufruf über die Benutzeroberfläche der App.

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

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 nach dem angegebenen Aufruf in der Liste der aktiven Aufrufe. Wenn der Aufruf nicht gefunden werden kann, wird das System benachrichtigt, und die Methode wird beendet. Wenn sie gefunden wird, wird die EndCall -Methode der ActiveCall -Klasse aufgerufen, um den Aufruf zu beenden, und das System ist eine Information, wenn er erfolgreich ist oder fehlschlägt. Bei erfolgreicher Ausführung wird der Aufruf aus der Auflistung der aktiven 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 eine Benachrichtigung erhält, dass ein neuer eingehender Anruf vorliegt, kann der Benutzer den ersten Anruf anhalten oder auflegen, um den zweiten Anruf entgegennehmen zu können.

In der oben angegebenen Situation sendet das System eine CXTransaction an die App, die eine Liste mehrerer Aktionen enthält (z. B. und CXEndCallAction ).CXAnswerCallAction Alle diese Aktionen müssen einzeln ausgeführt werden, damit das System die Benutzeroberfläche entsprechend aktualisieren kann.

Behandeln ausgehender Anrufe

Wenn der Benutzer auf einen Eintrag aus der Liste Zuletzt verwendete Elemente (in der Telefon-App) tippt, z. B. von einem Anruf, der zur App gehört, wird ihm vom System die Absicht "Anruf starten" gesendet:

Empfangen einer Startanrufabsicht

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

Weitere Informationen zu Absichten finden Sie in der Dokumentation zu Erweiterungen der Benutzeroberfläche für Absichten und Absichten .

Lebenszyklus des ausgehenden Anrufs

Wenn Sie mit CallKit und einem ausgehenden Anruf arbeiten, muss die App das System über die folgenden Lebenszyklusereignisse informieren:

  1. Starten : Informieren Sie das System darüber, dass ein ausgehender Anruf gestartet wird.
  2. Gestartet : Informieren Sie das System, dass ein ausgehender Anruf gestartet wurde.
  3. Verbinden : Informieren Sie das System darüber, dass der ausgehende Anruf eine Verbindung herstellt.
  4. Verbunden : Informieren Sie, dass der ausgehende Anruf verbunden ist und dass beide Parteien jetzt sprechen können.

Der folgende Code startet beispielsweise 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 in einem CXTransaction gebündelt wird, das mithilfe der -Methode der -Klasse an das RequestTransactionCXCallController System gesendet wird. Durch Aufrufen der RequestTransaction -Methode kann das System alle vorhandenen Aufrufe unabhängig von der Quelle (Telefon-App, FaceTime, VOIP usw.) in der Warteschleife platzieren, bevor der neue Anruf beginnt.

Die Anforderung, einen ausgehenden VOIP-Anruf zu starten, kann aus verschiedenen Quellen stammen, z. B. Siri, einem Eintrag in einer Kontakt-Karte (in der Kontakte-App) oder aus der Liste Zuletzt verwendete Anrufe (in der Telefon-App). In diesen Situationen wird der App eine Startanrufabsicht in einem NSUserActivity gesendet, und die AppDelegate muss dies 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 person abzurufen, die aufgerufen wird (siehe Oben Die StartCallRequest-Klasse ).

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 instance der ActiveCall -Klasse (um Informationen über den laufenden Anruf zu enthalten) und wird mit der person aufgefüllt, die aufgerufen wird. Die StartingConnectionChanged Ereignisse und ConnectedChanged werden verwendet, um den Lebenszyklus ausgehender Anrufe zu überwachen und zu melden. Der Aufruf wird gestartet, und das System hat mitgeteilt, dass die Aktion ausgeführt wurde.

Beenden eines ausgehenden Anrufs

Wenn der Benutzer einen ausgehenden Aufruf beendet hat und diesen 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ündelt sie in einerCXTransaction, die mithilfe der -Methode der CXCallController -Klasse an das RequestTransaction System gesendet wird.

Zusätzliche CallKit-Details

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

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

Anbieterkonfiguration

Die Anbieterkonfiguration ermöglicht es einer iOS 10-VOIP-App, die Benutzererfahrung (innerhalb der nativen benutzeroberfläche In-Call) bei der Arbeit mit CallKit anzupassen.

Eine App kann die folgenden Arten von Anpassungen vornehmen:

  • Zeigt einen lokalisierten Namen an.
  • Aktivieren Sie die Unterstützung für Videoanrufe.
  • Passen Sie die Schaltflächen auf der In-Call Ui 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, die CallKit verwenden, müssen Aktionen, die fehlschlagen, ordnungsgemäß behandeln und den Benutzer jederzeit über den Aktionszustand auf dem Laufenden halten.

Berücksichtigen Sie das folgende Beispiel:

  1. Die App hat eine Startanrufaktion erhalten und mit der Initialisierung eines neuen VOIP-Anrufs mit ihrem Kommunikationsnetzwerk begonnen.
  2. Aufgrund einer eingeschränkten oder keiner Netzwerkkommunikationsfunktion tritt bei dieser Verbindung ein Fehler auf.
  3. Die App muss die Fail-Nachricht zurück an die Aufrufaktion starten (Action.Fail()) senden, um das System über den Fehler zu informieren.
  4. Dadurch kann das System den Benutzer über die status des Anrufs informieren. Beispielsweise, um die Benutzeroberfläche für Anruffehler 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. Jedem von CallKit bereitgestellten Aktionstyp ist ein maximaler Timeoutwert zugeordnet. Diese Timeoutwerte stellen sicher, dass jede vom Benutzer angeforderte CallKit-Aktion reaktionsfähig behandelt wird, sodass das Betriebssystem auch flüssig und reaktionsfähig bleibt.

Es gibt mehrere Methoden für den Anbieterdelegat (CXProviderDelegate), die überschrieben werden sollten, um auch diese Timeoutsituationen 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 Do-Not-Disturb-Modus.

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 Verarbeitung der Audioressourcen, die eine iOS 10-VOIP-App während eines VOIP-Liveanrufs benötigt. Einer der größten Vorteile ist, dass die Audiositzung der App erhöhte Prioritäten hat, wenn sie in iOS 10 ausgeführt wird. Dies ist die gleiche Prioritätsstufe wie die integrierten Telefon- und FaceTime-Apps, und diese erweiterte 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 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 Startaufrufaktionssequenz

  1. Eine Anruf starten-Aktion wird von der App empfangen, um einen eingehenden Anruf entgegennehmen zu können.
  2. Bevor diese Aktion von der App ausgeführt wird, stellt sie die Konfiguration bereit, die für ihre 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 bereit AVAudioSession , die der von der App angeforderten Konfiguration entspricht. Die App wird über die DidActivateAudioSession -Methode ihres CXProviderDelegatebenachrichtigt.

Arbeiten mit Anrufverzeichniserweiterungen

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

Implementieren einer Anrufverzeichniserweiterung

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

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

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

  3. Wählen Sie iOS-Erweiterungen>>Verzeichniserweiterungen aufrufen 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 eine NSError , um den Fehler zu beschreiben, und übergeben Sie ihn an die CancelRequest -Methode der CXCallDirectoryExtensionContext -Klasse.

Verwenden Sie zum Festlegen der blockierten Zahlen die AddBlockingEntry -Methode der CXCallDirectoryExtensionContext -Klasse. Die für die -Methode bereitgestellten Zahlen müssen numerisch aufsteigend sein. Um eine optimale Leistung und Arbeitsspeicherauslastung bei vielen Telefonnummern zu erzielen, sollten Sie erwägen, nur eine Teilmenge von Nummern zu einem bestimmten Zeitpunkt zu laden und Autorelease-Pool(n) zu verwenden, um Objekte freizugeben, die während der einzelnen Zahlenbatchs zugeordnet sind, die geladen werden.

Um die Kontakt-App über die Kontaktnummern zu informieren, die der VOIP-App bekannt sind, verwenden Sie die AddIdentificationEntry -Methode der CXCallDirectoryExtensionContext -Klasse, und geben Sie sowohl die Nummer als auch eine Identifizierungsbezeichnung an. Auch hier müssen die für die -Methode bereitgestellten Zahlen numerisch aufsteigend sein. Um eine optimale Leistung und Arbeitsspeicherauslastung bei vielen Telefonnummern zu erzielen, sollten Sie erwägen, nur eine Teilmenge von Nummern zu einem bestimmten Zeitpunkt zu laden und Autorelease-Pool(n) zu verwenden, um Objekte freizugeben, die während der einzelnen Zahlenbatchs zugeordnet sind, die geladen werden.

Zusammenfassung

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