הטמעת CallKit ל-iOS (אינטגרציה עם שיחות מערכת)
נתקלנו במצבים שבהם אפליקציית VoIP מקבלת שיחה נכנסת, אך המשתמש מפספס אותה כי ההודעה המערכתית נראית כמו באנר רגיל. התוצאה: שיחות אבודות ולקוחות לא מרוצים. CallKit פותר זאת בכך שהוא הופך שיחת אפליקציה לשיחת מערכת מלאה עם מסך גדול, כפתורים ורישום בהיסטוריה. במאמר זה נסביר כיצד להגדיר את CallKit מאפס, להימנע מטעויות נפוצות ולהעלות את אחוז המענה.
כיצד CallKit משנה את חוויית המשתמש בשיחות נכנסות
ללא CallKit, שיחת VoIP נכנסת מופיעה כהודעת push. עם CallKit, היא מוצגת במסך מלא עם שם ותמונה של איש הקשר, ואפשרויות לענות או לדחות. לפי סטטיסטיקות, פורמט זה מעלה את אחוזי החיבור המוצלחים פי 3–4 בהשוואה להודעת push רגילה.
ארכיטקטורה: CXProvider ו-CXCallController
המחלקה המרכזית היא CXProvider. היא נקודת התקשורת בין האפליקציה למערכת: דרכה אנו מדווחים על שיחה נכנסת, מעדכנים מידע ומסיימים שיחה. המחלקה השנייה היא CXCallController, דרכה האפליקציה יוזמת ומנהלת שיחות יוצאות.
let providerConfiguration = CXProviderConfiguration()
providerConfiguration.supportsVideo = true
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.supportedHandleTypes = [.phoneNumber, .emailAddress, .generic]
provider = CXProvider(configuration: providerConfiguration)
provider.setDelegate(self, queue: nil)שיחה נכנסת. היא מגיעה דרך push מסוג VoIP (PushKit, לא APNs). זה חשוב: push רגיל מסוג APNs אינו תומך בשיחות CallKit מאז iOS 13 — אפל דורשת להשתמש ב-let providerConfiguration = CXProviderConfiguration() providerConfiguration.supportsVideo = true providerConfiguration.maximumCallsPerCallGroup = 1 providerConfiguration.supportedHandleTypes = [.phoneNumber, .emailAddress, .generic] provider = CXProvider(configuration: providerConfiguration) provider.setDelegate(self, queue: nil) וב-delegate PKPushType.voIP לקרוא מיד ל-PKPushRegistryDelegate.pushRegistry(_:didReceiveIncomingPushWith:). כל עיכוב בין ה-push לקריאה ל-reportNewIncomingCall יגרום למערכת לסגור את האפליקציה.
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: handle)
update.hasVideo = hasVideo
provider.reportNewIncomingCall(with: uuid, update: update) { error in
// если error != nil — система отклонила звонок (например, DND)
}
} מענה/דחייה.
המערכת קוראת למתודות reportNewIncomingCall: func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool) { let update = CXCallUpdate() update.remoteHandle = CXHandle(type: .generic, value: handle) update.hasVideo = hasVideo provider.reportNewIncomingCall(with: uuid, update: update) { error in // если error != nil — система отклонила звонок (например, DND) } } עם CXProviderDelegate או provider(_:perform:). בתגובה לפעולת מענה, יש לחבר את האודיו — להתחיל סשן WebRTC או לחבר את ערכת ה-VoIP שלך (Twilio Voice, Agora, Daily).
WebRTC וסשן אודיו
CallKit מנהל את סשן האודיו של המערכת. אסור להגדיר את CXAnswerCallAction בעצמך — זו האחריות של CallKit. כשהשיחה נענית, המערכת מפעילה את סשן האודיו וקוראת ל-CXEndCallAction. באותו רגע, חבר את זרם האודיו של WebRTC ל-AVAudioSession. כשהשיחה מסתיימת, נקרא provider(_:didActivate:), ואתה מנתק.
אם תקרא ל-AVAudioSession לפני הרגע הזה, השיחה עלולה ליפול או שהאודיו לא יעבוד. זה באג נפוץ באינטגרציות ראשונות.
Twilio Voice SDK: provider(_:didDeactivate:) → AVAudioSession.setActive(true) → ב-TwilioVoice.handleNotification הפעל אודיו דרך CallKit. ה-SDK עוטף חלק מהלוגיקה הזו, אבל עדיין תצטרך call.accept(with: delegate) משלך.
היסטוריית שיחות ו-Siri
לאחר סיום השיחה, קרא ל-callDidConnect. iOS מוסיף אוטומטית רשומה להיסטוריית השיחות באפליקציית הטלפון עם השם ומשך השיחה. המשתמש יכול להתקשר חזרה מאפליקציית הטלפון — זה יפתח שיחה דרך האפליקציה שלך אם ה-CXProvider מסוג provider.reportCall(with: uuid, endedAt: Date(), reason: .remoteEnded) תואם למזהה.
קיצורי Siri לשיחות: דרך CXHandle (iOS 13+), האפליקציה רושמת את ה-intent. "התקשר לאיוון באמצעות MyApp" — Siri מתחילה שיחה דרך CallKit.
בעיות נפוצות
שיחות כפולות.
ה-UUID חייב להיות ייחודי לכל שיחה ואסור שישתנה בין ה-push למענה. אם push מגיע פעמיים (ניסיון חוזר), בדוק את ה-UUID — אל תיצור .generic שני עבור אותו UUID.
שיחה תקועה בהיסטוריה. אם האפליקציה קורסת מבלי לקרוא ל-INStartCallIntent, השיחה נשארת כ"פעילה" בהיסטוריה. פתרון: בהפעלה הבאה של האפליקציה, בדוק את CXCallUpdate — אם יש שיחות שלא הסתיימו, סיים אותן.
VoIP Push ב-iOS 13+.
יש לאתחל את reportCall(endedAt:) ב-CXCallObserver.calls, לא באופן עצלן. אפל בודקת זאת ועלולה לסגור את האפליקציה.
השוואה: CallKit לעומת Push רגיל
| פרמטר | ללא CallKit | עם CallKit |
|---|---|---|
| תצוגת שיחה | באנר הודעת push | ממשק מסך מלא עם שם ותמונה |
| כפתורי פעולה | אין (רק "מאוחר יותר") | מענה, דחייה, תזכורת |
| רישום בהיסטוריית שיחות | לא | רישום אוטומטי באפליקציית הטלפון |
| אחוז מענה | ~20-30% | ~70-80% (בהתבסס על נתוני לקוחות) |
הסטטיסטיקות מבוססות על תיעוד Apple CallKit ומקרים אמיתיים.
כמה זמן לוקחת אינטגרציית CallKit?
יישום בסיסי של שיחות נכנסות/יוצאות עם ערכת VoIP אחת: 2–3 ימים. עם שיחות קבוצתיות, וידאו, קיצורי Siri וממשק משתמש מותאם: 4–5 ימים. העלות נקבעת לאחר ניתוח תשתית ה-VoIP והדרישות שלך. צור קשר — נעריך את הפרויקט שלך ונציע את הפתרון האופטימלי.
מה כלול בעבודה
- הגדרת
PKPushRegistryו-application(_:didFinishLaunchingWithOptions:)עם תצורה המותאמת לצרכי האפליקציה - אינטגרציה של PushKit ל-VoIP push
- מטפלים למענה, דחייה, סיום, השהאה והשתקה
- חיבור ל-WebRTC/ערכת VoIP (Twilio Voice, Agora, Daily, Vonage)
- ניהול סשן אודיו דרך מחזור החיים של CallKit
- רישום היסטוריית שיחות
- טיפול בתרחישי קריסה וניקוי שיחות שלא הסתיימו
אנו מבטיחים פעולה תקינה של CallKit בהתאם לתיעוד של אפל. במשך יותר מ-5 שנים, יישמנו יותר מ-30 פרויקטים עם VoIP ו-CallKit, כך שאנו מכירים את כל המלכודות.







