Bagikan melalui


CallKit di Xamarin.iOS

Api CallKit baru di iOS 10 menyediakan cara bagi aplikasi VOIP untuk berintegrasi dengan I Telepon UI dan memberikan antarmuka dan pengalaman yang familier kepada pengguna akhir. Dengan api ini pengguna dapat melihat dan berinteraksi dengan panggilan VOIP dari Layar Kunci perangkat iOS dan mengelola kontak menggunakan tampilan Favorit dan Terbaru aplikasi Telepon.

Tentang CallKit

Menurut Apple, CallKit adalah kerangka kerja baru yang akan meningkatkan aplikasi Voice Over IP (VOIP) pihak ke-3 ke pengalaman pihak ke-1 di iOS 10. Api CallKit memungkinkan aplikasi VOIP untuk berintegrasi dengan I Telepon UI dan memberikan antarmuka dan pengalaman yang familier kepada pengguna akhir. Sama seperti aplikasi Telepon bawaan, pengguna dapat melihat dan berinteraksi dengan panggilan VOIP dari Layar Kunci perangkat iOS dan mengelola kontak menggunakan tampilan Favorit dan Terbaru aplikasi Telepon.

Selain itu, API CallKit menyediakan kemampuan untuk membuat Ekstensi Aplikasi yang dapat mengaitkan nomor telepon dengan nama (ID Pemanggil) atau memberi tahu sistem kapan nomor harus diblokir (Pemblokiran Panggilan).

Pengalaman aplikasi VOIP yang ada

Sebelum membahas Api CallKit baru dan kemampuannya, lihat pengalaman pengguna saat ini dengan aplikasi VOIP pihak ke-3 di iOS 9 (dan lebih sedikit) menggunakan aplikasi VOIP fiktif bernama MonkeyCall. MonkeyCall adalah aplikasi sederhana yang memungkinkan pengguna mengirim dan menerima panggilan VOIP menggunakan API iOS yang ada.

Saat ini, jika pengguna menerima panggilan masuk di MonkeyCall dan i mereka Telepon dikunci, pemberitahuan yang diterima di layar Kunci tidak dapat dibedakan dari jenis pemberitahuan lain (seperti yang berasal dari aplikasi Pesan atau Email misalnya).

Jika pengguna ingin menjawab panggilan, mereka harus menggeser pemberitahuan MonkeyCall untuk membuka aplikasi dan memasukkan kode sandi mereka (atau ID Sentuh pengguna) untuk membuka kunci telepon sebelum mereka dapat menerima panggilan dan memulai percakapan.

Pengalaman ini sama rumitnya jika ponsel tidak terkunci. Sekali lagi, panggilan MonkeyCall masuk ditampilkan sebagai banner pemberitahuan standar yang meluncur masuk dari bagian atas layar. Karena pemberitahuan bersifat sementara, dapat dengan mudah dilewatkan oleh pengguna yang memaksa mereka untuk membuka Pusat Pemberitahuan dan menemukan pemberitahuan khusus untuk menjawab kemudian memanggil atau menemukan dan meluncurkan aplikasi MonkeyCall secara manual.

Pengalaman aplikasi CallKit VOIP

Dengan menerapkan API CallKit baru di aplikasi MonkeyCall, pengalaman pengguna dengan panggilan VOIP masuk dapat sangat ditingkatkan di iOS 10. Ambil contoh pengguna yang menerima panggilan VOIP saat ponsel mereka dikunci dari atas. Dengan menerapkan CallKit, panggilan akan muncul di layar Kunci i Telepon, seperti halnya jika panggilan diterima dari aplikasi Telepon bawaan, dengan UI layar penuh, asli, dan fungsionalitas gesek-ke-jawaban standar.

Sekali lagi, jika i Telepon tidak terkunci saat panggilan MONKEYCall VOIP diterima, antarmuka pengguna asli layar penuh yang sama, dan fungsionalitas geser ke jawaban standar dan ketuk untuk menolak aplikasi Telepon bawaan disajikan dan MonkeyCall memiliki opsi untuk memutar nada dering kustom.

CallKit menyediakan fungsionalitas tambahan untuk MonkeyCall, memungkinkan panggilan VOIP-nya berinteraksi dengan jenis panggilan lain, untuk muncul di daftar Terbaru dan Favorit bawaan, untuk menggunakan fitur Jangan Ganggu dan Blokir bawaan, mulai panggilan MonkeyCall dari Siri dan menawarkan kemampuan bagi pengguna untuk menetapkan panggilan MonkeyCall kepada orang-orang di aplikasi Kontak.

Bagian berikut akan mencakup arsitektur CallKit, alur panggilan masuk dan keluar, dan API CallKit secara rinci.

Arsitektur CallKit

Di iOS 10, Apple telah mengadopsi CallKit di semua Layanan Sistem sehingga panggilan yang dilakukan di CarPlay, misalnya, diketahui oleh UI Sistem melalui CallKit. Dalam contoh yang diberikan di bawah ini, karena MonkeyCall mengadopsi CallKit, itu dikenal oleh Sistem dengan cara yang sama seperti Layanan Sistem bawaan ini dan mendapatkan semua fitur yang sama:

Tumpukan Layanan CallKit

Lihat lebih dekat Aplikasi MonkeyCall dari diagram di atas. Aplikasi ini berisi semua kodenya untuk berkomunikasi dengan jaringannya sendiri dan berisi Antarmuka Penggunanya sendiri. Ini menautkan di CallKit untuk berkomunikasi dengan sistem:

Arsitektur Aplikasi MonkeyCall

Ada dua antarmuka utama di CallKit yang digunakan aplikasi:

  • CXProvider - Ini memungkinkan aplikasi MonkeyCall untuk menginformasikan sistem pemberitahuan di luar band yang mungkin terjadi.
  • CXCallController - Memungkinkan aplikasi MonkeyCall untuk menginformasikan sistem tindakan pengguna lokal.

The CXProvider

Seperti yang dinyatakan di atas, CXProvider memungkinkan aplikasi untuk menginformasikan sistem pemberitahuan di luar band yang mungkin terjadi. Ini adalah pemberitahuan yang tidak terjadi karena tindakan pengguna lokal, tetapi terjadi karena peristiwa eksternal seperti panggilan masuk.

Aplikasi harus menggunakan CXProvider untuk hal berikut:

  • Laporkan panggilan masuk ke Sistem.
  • Laporkan bahwa panggilan keluar telah tersambung ke Sistem.
  • Laporkan pengguna jarak jauh yang mengakhiri panggilan ke Sistem.

Ketika aplikasi ingin berkomunikasi dengan sistem, aplikasi menggunakan CXCallUpdate kelas dan ketika Sistem perlu berkomunikasi dengan aplikasi, aplikasi menggunakan CXAction kelas :

Berkomunikasi dengan sistem melalui CXProvider

The CXCallController

memungkinkan CXCallController aplikasi untuk menginformasikan sistem tindakan pengguna lokal seperti pengguna yang memulai panggilan VOIP. Dengan mengimplementasikan CXCallController aplikasi, aplikasi akan saling berhubungan dengan jenis panggilan lain dalam sistem. Misalnya, jika sudah ada panggilan telepon aktif yang sedang berlangsung, CXCallController dapat memungkinkan aplikasi VOIP untuk melakukan panggilan tersebut ditangguhkan dan memulai atau menjawab panggilan VOIP.

Aplikasi harus menggunakan CXCallController untuk hal berikut:

  • Laporkan ketika pengguna telah memulai panggilan keluar ke Sistem.
  • Laporkan saat pengguna menjawab panggilan masuk ke Sistem.
  • Laporkan saat pengguna mengakhiri panggilan ke Sistem.

Ketika aplikasi ingin mengkomunikasikan tindakan pengguna lokal ke sistem, aplikasi menggunakan CXTransaction kelas :

Melaporkan ke sistem menggunakan CXCallController

Menerapkan CallKit

Bagian berikut akan menunjukkan cara mengimplementasikan CallKit di aplikasi Xamarin.iOS VOIP. Demi contoh, dokumen ini akan menggunakan kode dari aplikasi MonkeyCall VOIP fiktif. Kode yang disajikan di sini mewakili beberapa kelas pendukung, bagian spesifik CallKit akan tercakup secara rinci di bagian berikut.

Kelas ActiveCall

Kelas ActiveCall ini digunakan oleh aplikasi MonkeyCall untuk menyimpan semua informasi tentang panggilan VOIP yang saat ini aktif sebagai berikut:

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 menyimpan beberapa properti yang menentukan status panggilan dan dua peristiwa yang dapat dinaikkan saat status panggilan berubah. Karena ini adalah contoh saja, ada tiga metode yang digunakan untuk mensimulasikan memulai, menjawab, dan mengakhiri panggilan.

Kelas StartCallRequest

Kelas StartCallRequest statis, menyediakan beberapa metode pembantu yang akan digunakan saat bekerja dengan panggilan keluar:

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

Kelas CallHandleFromURL dan CallHandleFromActivity digunakan di AppDelegate untuk mendapatkan handel kontak orang yang dipanggil dalam panggilan keluar. Untuk informasi selengkapnya, silakan lihat bagian Menangani Panggilan Keluar di bawah ini.

Kelas ActiveCallManager

Kelas ActiveCallManager menangani semua panggilan terbuka di aplikasi MonkeyCall.

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

Sekali lagi, karena ini hanya simulasi, satu-satunya ActiveCallManager mempertahankan kumpulan ActiveCall objek dan memiliki rutinitas untuk menemukan panggilan tertentu oleh propertinya UUID . Ini juga mencakup metode untuk memulai, mengakhiri, dan mengubah status ditangguhkan dari panggilan keluar. Untuk informasi selengkapnya, silakan lihat bagian Menangani Panggilan Keluar di bawah ini.

Kelas ProviderDelegate

Seperti yang dibahas di atas, menyediakan CXProvider komunikasi dua arah antara aplikasi dan Sistem untuk pemberitahuan di luar band. Pengembang perlu menyediakan kustom CXProviderDelegate dan melampirkannya ke CXProvider aplikasi untuk menangani peristiwa CallKit di luar band. MonkeyCall menggunakan yang berikut 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
    }
}

Ketika instans delegasi ini dibuat, instans tersebut akan diteruskan ActiveCallManager untuk menangani aktivitas panggilan apa pun. Selanjutnya, ini mendefinisikan jenis handel (CXHandleType) yang CXProvider akan merespons:

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

Dan mendapatkan gambar templat yang akan diterapkan ke ikon aplikasi saat panggilan sedang berlangsung:

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

Nilai-nilai ini dibundel ke dalam CXProviderConfiguration yang akan digunakan untuk mengonfigurasi CXProvider:

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

Delegasi kemudian membuat baru CXProvider dengan konfigurasi ini dan melampirkan dirinya sendiri ke dalamnya:

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

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

Saat menggunakan CallKit, aplikasi tidak akan lagi membuat dan menangani sesi audionya sendiri, sebaliknya perlu mengonfigurasi dan menggunakan sesi audio yang akan dibuat dan ditangani Sistem untuk itu.

Jika ini adalah aplikasi nyata, DidActivateAudioSession metode ini akan digunakan untuk memulai panggilan dengan prakonfigurasi AVAudioSession yang disediakan Sistem:

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

Ini juga akan menggunakan DidDeactivateAudioSession metode untuk menyelesaikan dan merilis koneksinya ke sesi audio yang disediakan Sistem:

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

Sisa kode akan dibahas secara rinci di bagian berikut.

Kelas AppDelegate

MonkeyCall menggunakan AppDelegate untuk menyimpan instans ActiveCallManager dan CXProviderDelegate yang akan digunakan di seluruh aplikasi:

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

Metode OpenUrl penimpaan dan ContinueUserActivity digunakan saat aplikasi memproses panggilan keluar. Untuk informasi selengkapnya, silakan lihat bagian Menangani Panggilan Keluar di bawah ini.

Menangani panggilan masuk

Ada beberapa status dan proses yang dapat dilalui panggilan VOIP masuk selama alur kerja panggilan masuk umum seperti:

  • Memberi tahu pengguna (dan Sistem) bahwa panggilan masuk ada.
  • Menerima pemberitahuan ketika pengguna ingin menjawab panggilan dan menginisialisasi panggilan dengan pengguna lain.
  • Beri tahu Sistem dan Jaringan Komunikasi ketika pengguna ingin mengakhiri panggilan saat ini.

Bagian berikut akan melihat secara rinci bagaimana aplikasi dapat menggunakan CallKit untuk menangani alur kerja panggilan masuk, sekali lagi menggunakan aplikasi MonkeyCall VOIP sebagai contoh.

Memberi tahu pengguna tentang panggilan masuk

Saat pengguna jarak jauh telah memulai percakapan VOIP dengan pengguna lokal, hal berikut ini terjadi:

Pengguna jarak jauh telah memulai percakapan VOIP

  1. Aplikasi ini mendapatkan pemberitahuan dari Communications Network-nya bahwa ada panggilan VOIP masuk.
  2. Aplikasi ini menggunakan CXProvider untuk mengirim CXCallUpdate ke Sistem yang menginformasikan panggilan.
  3. Sistem menerbitkan panggilan ke UI Sistem, Layanan Sistem, dan aplikasi VOIP lainnya menggunakan CallKit.

Misalnya, dalam 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);
        }
    });
}

Kode ini membuat instans baru CXCallUpdate dan melampirkan handel ke instans yang akan mengidentifikasi pemanggil. Selanjutnya, menggunakan ReportNewIncomingCall metode CXProvider kelas untuk menginformasikan sistem panggilan. Jika berhasil, panggilan ditambahkan ke kumpulan panggilan aktif aplikasi, jika tidak, kesalahan perlu dilaporkan kepada pengguna.

Pengguna menjawab panggilan masuk

Jika pengguna ingin menjawab panggilan VOIP masuk, hal berikut terjadi:

Pengguna menjawab panggilan VOIP masuk

  1. UI Sistem memberi tahu Sistem bahwa pengguna ingin menjawab panggilan VOIP.
  2. Sistem mengirimkan CXAnswerCallAction ke aplikasi CXProvider yang menginformasikannya tentang Niat Jawaban.
  3. Aplikasi ini menginformasikan Jaringan Komunikasinya bahwa pengguna menjawab panggilan dan panggilan VOIP berjalan seperti biasa.

Misalnya, dalam 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 ();
        }
    });
}

Kode ini pertama-tama mencari panggilan yang diberikan dalam daftar panggilan aktifnya. Jika panggilan tidak dapat ditemukan, sistem akan diberi tahu dan metode keluar. Jika ditemukan, AnswerCall metode kelas dipanggil ActiveCall untuk memulai panggilan dan Sistem adalah informasi jika berhasil atau gagal.

Pengguna mengakhiri panggilan masuk

Jika pengguna ingin menghentikan panggilan dari dalam UI aplikasi, hal berikut terjadi:

Pengguna mengakhiri panggilan dari dalam UI aplikasi

  1. Aplikasi membuat CXEndCallAction yang dibundel ke dalam CXTransaction yang dikirim ke Sistem untuk memberi tahunya bahwa panggilan berakhir.
  2. Sistem memverifikasi Niat Akhiri Panggilan dan mengirim CXEndCallAction kembali ke aplikasi melalui CXProvider.
  3. Aplikasi kemudian menginformasikan Jaringan Komunikasinya bahwa panggilan berakhir.

Misalnya, dalam 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 ();
        }
    });
}

Kode ini pertama-tama mencari panggilan yang diberikan dalam daftar panggilan aktifnya. Jika panggilan tidak dapat ditemukan, sistem akan diberi tahu dan metode keluar. Jika ditemukan, EndCall metode kelas dipanggil ActiveCall untuk mengakhiri panggilan dan Sistem adalah informasi jika berhasil atau gagal. Jika berhasil, panggilan akan dihapus dari kumpulan panggilan aktif.

Mengelola beberapa panggilan

Sebagian besar aplikasi VOIP dapat menangani beberapa panggilan sekaligus. Misalnya, jika saat ini ada panggilan VOIP aktif dan aplikasi mendapatkan pemberitahuan bahwa ada panggilan masuk baru, pengguna dapat menjeda atau menutup panggilan pertama untuk menjawab panggilan kedua.

Dalam situasi yang diberikan di atas, Sistem akan mengirim CXTransaction ke aplikasi yang akan menyertakan daftar beberapa tindakan (seperti CXEndCallAction dan CXAnswerCallAction). Semua tindakan ini perlu dipenuhi secara individual, sehingga Sistem dapat memperbarui UI dengan tepat.

Menangani panggilan keluar

Jika pengguna mengetuk entri dari daftar Terbaru (di aplikasi Telepon), misalnya, yang berasal dari panggilan milik aplikasi, itu akan dikirimi Niat Mulai Panggilan oleh sistem:

Menerima Niat Mulai Panggilan

  1. Aplikasi akan membuat Tindakan Mulai Panggilan berdasarkan Niat Mulai Panggilan yang diterimanya dari Sistem.
  2. Aplikasi akan menggunakan CXCallController untuk meminta Tindakan Mulai Panggilan dari sistem.
  3. Jika Sistem menerima Tindakan, tindakan tersebut akan dikembalikan ke aplikasi melalui XCProvider delegasi.
  4. Aplikasi memulai panggilan keluar dengan Communication Network-nya.

Untuk informasi selengkapnya tentang Niat, silakan lihat dokumentasi Ekstensi UI Niat dan Niat kami.

Siklus hidup panggilan keluar

Saat bekerja dengan CallKit dan panggilan keluar, aplikasi harus menginformasikan Sistem peristiwa siklus hidup berikut:

  1. Memulai - Menginformasikan sistem bahwa panggilan keluar akan dimulai.
  2. Dimulai - Beri tahu sistem bahwa panggilan keluar telah dimulai.
  3. Koneksi - Beri tahu sistem bahwa panggilan keluar tersambung.
  4. Koneksi - Beri tahu panggilan keluar telah terhubung dan kedua belah pihak dapat berbicara sekarang.

Misalnya, kode berikut akan memulai panggilan keluar:

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

Ini membuat CXHandle dan menggunakannya untuk mengonfigurasi CXStartCallAction yang dibundel ke dalam CXTransaction yang dikirim ke Sistem menggunakan RequestTransaction metode CXCallController kelas . Dengan memanggil RequestTransaction metode , Sistem dapat melakukan panggilan yang ada saat ditangguhkan, apa pun sumbernya (aplikasi Telepon, FaceTime, VOIP, dll.), sebelum panggilan baru dimulai.

Permintaan untuk memulai panggilan VOIP keluar dapat berasal dari beberapa sumber yang berbeda, seperti Siri, entri pada kartu Kontak (di aplikasi Kontak) atau dari daftar Terbaru (di aplikasi Telepon). Dalam situasi ini, aplikasi akan dikirimi Niat Mulai Panggilan di dalam dan NSUserActivity AppDelegate perlu menanganinya:

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

CallHandleFromActivity Di sini metode kelas StartCallRequest pembantu sedang digunakan untuk mendapatkan pegangan kepada orang yang dipanggil (lihat Kelas StartCallRequest di atas).

Metode PerformStartCallActionKelas ProviderDelegate digunakan untuk akhirnya memulai panggilan keluar aktual dan menginformasikan Sistem siklus hidupnya:

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

Ini membuat instans ActiveCall kelas (untuk menyimpan informasi tentang panggilan yang sedang berlangsung) dan mengisi dengan orang yang dipanggil. Peristiwa StartingConnectionChanged dan ConnectedChanged digunakan untuk memantau dan melaporkan siklus hidup panggilan keluar. Panggilan dimulai dan Sistem menginformasikan bahwa Tindakan telah terpenuhi.

Mengakhiri panggilan keluar

Ketika pengguna telah selesai dengan panggilan keluar dan ingin mengakhirinya, kode berikut dapat digunakan:

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

Jika membuat CXEndCallAction dengan UUID panggilan ke akhir, bundelkan dalam CXTransaction yang dikirim ke Sistem menggunakan RequestTransaction metode CXCallController kelas .

Detail CallKit tambahan

Bagian ini akan membahas beberapa detail tambahan yang perlu dipertimbangkan pengembang saat bekerja dengan CallKit seperti:

  • Konfigurasi Penyedia
  • Kesalahan Tindakan
  • Pembatasan Sistem
  • VOIP Audio

Konfigurasi penyedia layanan

Konfigurasi penyedia memungkinkan aplikasi VOIP iOS 10 menyesuaikan pengalaman pengguna (di dalam UI In-Call asli) saat bekerja dengan CallKit.

Aplikasi dapat membuat jenis kustomisasi berikut:

  • Tampilkan nama yang dilokalkan.
  • Aktifkan dukungan panggilan video.
  • Sesuaikan tombol pada UI Dalam Panggilan dengan menyajikan ikon gambar templatnya sendiri. Interaksi pengguna dengan tombol kustom dikirim langsung ke aplikasi untuk diproses.

Kesalahan tindakan

Aplikasi VOIP iOS 10 yang menggunakan CallKit perlu menangani Tindakan yang gagal dengan anggun dan membuat pengguna mendapatkan informasi tentang status Tindakan setiap saat.

Pertimbangkan contoh berikut:

  1. Aplikasi ini telah menerima Tindakan Mulai Panggilan dan telah memulai proses inisialisasi panggilan VOIP baru dengan Jaringan Komunikasinya.
  2. Karena kemampuan komunikasi jaringan terbatas atau tidak ada, koneksi ini gagal.
  3. Aplikasi harus mengirim pesan Gagal kembali ke Tindakan Mulai Panggilan (Action.Fail()) untuk menginformasikan Sistem kegagalan.
  4. Ini memungkinkan Sistem untuk memberi tahu pengguna tentang status panggilan. Misalnya, untuk menampilkan UI Kegagalan Panggilan.

Selain itu, aplikasi VOIP iOS 10 harus merespons Kesalahan Waktu Habis yang dapat terjadi ketika Tindakan yang diharapkan tidak dapat diproses dalam waktu tertentu. Setiap Jenis Tindakan yang disediakan oleh CallKit memiliki nilai Batas Waktu maksimum yang terkait dengannya. Nilai Timeout ini memastikan bahwa Setiap Tindakan CallKit yang diminta oleh pengguna ditangani dengan cara yang responsif, sehingga menjaga cairan OS dan responsif juga.

Ada beberapa metode pada Delegasi Penyedia (CXProviderDelegate) yang harus ditimpa untuk menangani situasi Batas Waktu ini dengan baik juga.

Pembatasan sistem

Berdasarkan status perangkat iOS saat ini yang menjalankan aplikasi VOIP iOS 10, pembatasan sistem tertentu dapat diberlakukan.

Misalnya, panggilan VOIP masuk dapat dibatasi oleh Sistem jika:

  1. Orang yang memanggil ada di Daftar Pemanggil yang Diblokir pengguna.
  2. Perangkat iOS pengguna berada dalam mode Jangan Diganggu.

Jika panggilan VOIP dibatasi oleh salah satu situasi ini, gunakan kode berikut untuk menanganinya:

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

}

Audio VOIP

CallKit memberikan beberapa manfaat untuk menangani sumber daya audio yang akan diperlukan aplikasi VOIP iOS 10 selama panggilan VOIP langsung. Salah satu manfaat terbesar adalah sesi audio aplikasi akan memiliki prioritas yang ditingkatkan saat berjalan di iOS 10. Ini adalah tingkat prioritas yang sama dengan aplikasi Telepon dan FaceTime bawaan dan tingkat prioritas yang ditingkatkan ini akan mencegah aplikasi lain yang berjalan mengganggu sesi audio aplikasi VOIP.

Selain itu, CallKit memiliki akses ke petunjuk perutean audio lain yang dapat meningkatkan performa dan dengan cerdas merutekan audio VOIP ke perangkat output tertentu selama panggilan langsung berdasarkan preferensi pengguna dan status perangkat. Misalnya, berdasarkan perangkat yang terpasang seperti headphone bluetooth, koneksi CarPlay langsung, atau pengaturan Aksesibilitas.

Selama siklus hidup panggilan VOIP yang khas menggunakan CallKit, aplikasi harus mengonfigurasi Aliran Audio yang akan disediakan CallKit. Lihat contoh berikut:

Urutan Tindakan Mulai Panggilan

  1. Tindakan Mulai Panggilan diterima oleh aplikasi untuk menjawab panggilan masuk.
  2. Sebelum Tindakan ini dipenuhi oleh aplikasi, tindakan ini menyediakan konfigurasi yang akan diperlukan untuknya AVAudioSession.
  3. Aplikasi ini memberi tahu Sistem bahwa Tindakan telah terpenuhi.
  4. Sebelum panggilan tersambung, CallKit menyediakan prioritas AVAudioSession tinggi yang cocok dengan konfigurasi yang diminta aplikasi. Aplikasi akan diberi tahu melalui DidActivateAudioSession metode .CXProviderDelegate

Bekerja dengan ekstensi direktori panggilan

Saat bekerja dengan CallKit, Ekstensi Direktori Panggilan menyediakan cara untuk menambahkan nomor panggilan yang diblokir dan mengidentifikasi nomor yang khusus untuk aplikasi VOIP tertentu untuk kontak di aplikasi Kontak di perangkat iOS.

Menerapkan ekstensi direktori Panggilan

Untuk menerapkan Ekstensi Direktori Panggilan di aplikasi Xamarin.iOS, lakukan hal berikut:

  1. Buka solusi aplikasi di Visual Studio untuk Mac.

  2. Klik kanan Pada Nama Solusi di Penjelajah Solusi dan pilih Tambahkan>Proyek Baru.

  3. Pilih Ekstensi iOS>>Panggil Ekstensi Direktori dan klik tombol Berikutnya:

    Membuat Ekstensi Direktori Panggilan baru

  4. Masukkan Nama untuk ekstensi dan klik tombol Berikutnya :

    Memasukkan nama untuk ekstensi

  5. Sesuaikan Nama Proyek dan/atau Nama Solusi jika diperlukan dan klik tombol Buat :

    Membuat proyek

Ini akan menambahkan CallDirectoryHandler.cs kelas ke proyek yang terlihat seperti berikut ini:

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

Metode BeginRequest dalam Penanganan Direktori Panggilan perlu dimodifikasi untuk menyediakan fungsionalitas yang diperlukan. Dalam kasus sampel di atas, ia mencoba mengatur daftar nomor yang diblokir dan tersedia dalam database kontak aplikasi VOIP. Jika salah satu permintaan gagal karena alasan apa pun, buat NSError untuk menjelaskan kegagalan dan meneruskannya metode CancelRequestCXCallDirectoryExtensionContext kelas.

Untuk mengatur angka yang diblokir, gunakan AddBlockingEntry metode CXCallDirectoryExtensionContext kelas . Angka yang disediakan untuk metode harus dalam urutan naik numerik. Untuk performa optimal dan penggunaan memori ketika ada banyak nomor telepon, pertimbangkan untuk hanya memuat subset angka pada waktu tertentu dan menggunakan kumpulan penghapusan otomatis untuk melepaskan objek yang dialokasikan selama setiap batch nomor yang dimuat.

Untuk menginformasikan ke Aplikasi kontak dari nomor kontak yang diketahui oleh aplikasi VOIP, gunakan AddIdentificationEntry metode CXCallDirectoryExtensionContext kelas dan berikan nomor dan label identifikasi. Sekali lagi, angka yang disediakan untuk metode harus dalam urutan naik numerik. Untuk performa optimal dan penggunaan memori ketika ada banyak nomor telepon, pertimbangkan untuk hanya memuat subset angka pada waktu tertentu dan menggunakan kumpulan penghapusan otomatis untuk melepaskan objek yang dialokasikan selama setiap batch nomor yang dimuat.

Ringkasan

Artikel ini telah membahas CallKit API baru yang dirilis Apple di iOS 10 dan cara menerapkannya di aplikasi VOIP Xamarin.iOS. Ini telah menunjukkan bagaimana CallKit memungkinkan aplikasi untuk diintegrasikan ke dalam Sistem iOS, bagaimana aplikasi tersebut menyediakan paritas fitur dengan aplikasi bawaan (seperti Telepon) dan bagaimana aplikasi meningkatkan visibilitas aplikasi di seluruh iOS di lokasi seperti Layar Kunci dan Beranda, melalui interaksi Siri dan melalui aplikasi Kontak.