iOS SDK
Swift 5.9, iOS 15+; SPM ve CocoaPods. APNs push, Notification Service Extension ile zengin medya ve teslimat onayı, in-app, bildirim kutusu, tercihler.
Hedefler: Pushizo (uygulama), PushizoExtension (Notification Service Extension), PushizoCore (ortak tipler; import Pushizo yeniden dışa aktarır). Swizzling kullanılmaz; APNs token'ı açıkça SDK'ya iletilir. Örnek: examples/ios-swiftui.
Kurulum
- Apple Developer → Keys → APNs anahtarı (
.p8), Key ID, Team ID. Dashboard → Kanallar → iOS Push →.p8, Key ID, Team ID, Bundle ID, ortam (sandbox Xcode build, production TestFlight/App Store) → Doğrula. - Xcode → Signing & Capabilities → Push Notifications ve Background Modes → Remote notifications.
- Paket:
- SPM: File → Add Package Dependencies →
sdk/ios(ya da yayınlanmış depo). Uygulama hedefinePushizo, NSE hedefinePushizoExtension. - CocoaPods:
pod 'Pushizo'(NSE hedefindepod 'Pushizo/Extension'); monorepo içinden:path => '<repo>/sdk/ios'.
- SPM: File → Add Package Dependencies →
Başlatma (AppDelegate)
import Pushizo
final class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
Pushizo.initialize("app_…", launchOptions: launchOptions) { options in
options.apiUrl = "https://api.example.com"
options.requiresPrivacyConsent = false
options.provisionalAuthorization = false // sessiz (provisional) izin
options.appGroup = "group.com.example.app" // NSE ile paylaşılan depolama (isteğe bağlı)
options.logLevel = .warn
}
Pushizo.Notifications.addClickListener { event in
// event.notification, event.actionId, event.url
}
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Pushizo.setDeviceToken(deviceToken)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
Pushizo.setDeviceTokenError(error)
}
}SwiftUI'de @UIApplicationDelegateAdaptor(AppDelegate.self) ile bağlayın. SDK UNUserNotificationCenter.current().delegate olarak kendi delegesini atar (ön plan gösterimi ve tıklama → opened). Sandbox/production ortamı embedded.mobileprovision'dan tespit edilir; options.apnsEnvironment ile ezilebilir.
Notification Service Extension
File → New → Target → Notification Service Extension:
import PushizoExtension
final class NotificationService: UNNotificationServiceExtension {
private let hs = PushizoNotificationServiceExtension(appId: "app_…", apiUrl: "https://api.example.com", appGroup: nil)
override func didReceive(_ request: UNNotificationRequest, withContentHandler handler: @escaping (UNNotificationContent) -> Void) {
hs.didReceive(request, withContentHandler: handler)
}
override func serviceExtensionTimeWillExpire() { hs.serviceExtensionTimeWillExpire() }
}Pushizo görünür bildirimlerde mutable-content: 1 gönderir; NSE görseli indirir, rozet artırmayı uygular ve received (confirmed delivery) kaydını yapar. NSE yoksa Mesaj İzleme'de yalnızca sent ve opened görünür.
API
Pushizo.setConsentGiven(true)
Pushizo.login("user_42", identityHash: nil); Pushizo.logout()
Pushizo.User.addTag("plan", "pro"); Pushizo.User.addTags(["a": "1"]); Pushizo.User.removeTag("plan"); Pushizo.User.getTags()
Pushizo.User.setLanguage("tr"); Pushizo.User.id; Pushizo.User.externalId
Pushizo.User.pushSubscription.optIn() / optOut() / id / token
let state = await Pushizo.User.pushSubscription.state()
Pushizo.User.pushSubscription.addObserver { state in … }
Pushizo.Notifications.requestPermission { granted in … }
let granted = await Pushizo.Notifications.permission()
let permissionToken = Pushizo.Notifications.addPermissionObserver { granted in … } // HSListenerToken
Pushizo.Notifications.removePermissionObserver(permissionToken)
let fgToken = Pushizo.Notifications.addForegroundLifecycleListener { e in e.preventDefault() }
let clickToken = Pushizo.Notifications.addClickListener { e in … }
Pushizo.Notifications.removeClickListener(clickToken)
Pushizo.Session.sendEvent("purchase", properties: ["sku": "A1"], value: 49.9)
Pushizo.Debug.setLogLevel(.debug)addPermissionObserveretkin izin (tam ya da provisional) değiştiğinde ana iş parçacığında çağrılır:requestPermissionsonuçlandığında ve uygulama yeniden etkinleştiğinde (izin Ayarlar'dan değişmiş olabilir). İlk durum bildirilmez.- Dinleyici ekleyen metotlar
HSListenerTokendöndürür (@discardableResult); kaldırma belirteçle yapılır. requestPermission[.alert, .badge, .sound](+.provisional) ister; critical alert izni için critical alert rehberi.
E-posta ve SMS
Pushizo.User.addEmail("Ali@Example.com") // → ali@example.com; geçersizse günlüğe hata, istek yapılmaz
Pushizo.User.removeEmail("ali@example.com")
Pushizo.User.addSms("+90 (532) 123-45-67") // → +905321234567
Pushizo.User.removeSms("+905321234567")
Pushizo.User.emails; Pushizo.User.smsNumbers
HSContacts.normalizeEmail(text) != nil // formda ön doğrulamaKayıt POST /sdk/v1/apps/{app}/subscriptions (channel: email|sms): kimlikli kullanıcıda external_id (+ identity_hash), anonimde cihazın mevcut aboneliği user.subscription_id. Login sonrası e-posta/SMS abonelikleri de aynı kullanıcıya bağlanır; logout'ta abonelikler önceki kullanıcıda kalır.
Kullanıcı tercihleri
let result = try await Pushizo.User.getPreferences() // result.preferences: [HSPreference], result.categories: [HSMessageCategory]
try await Pushizo.User.setPreferences([
HSPreferenceInput(category: "promos", optedIn: false), // tüm kanallarda kapat
HSPreferenceInput(category: "orders", channel: "ios_push", optedIn: true),
])
Pushizo.User.getPreferences { result in … } // geri çağrı sürümleriHSPreference: category, channel (nil = tüm kanallar), optedIn, explicit, source, updatedAt. Abonelik henüz yoksa HSError.noSubscription, geçersiz girdi HSError.invalidArgument.
In-app mesajlar
Pushizo.InAppMessages.addTrigger("level", "gold") // addTriggers, removeTrigger(s), clearTriggers(), getTriggers()
Pushizo.InAppMessages.setScreen("cart") // SwiftUI: .onAppear { … } / .onDisappear { setScreen(nil) }
Pushizo.Session.sendEvent("cart_viewed")
Pushizo.InAppMessages.paused = true
Pushizo.InAppMessages.addClickListener { e in /* e.messageId, e.actionId, e.url */ }
final class InAppLogger: HSInAppLifecycleListener { // tüm metotlar isteğe bağlı
func onDidDisplay(_ message: HSInAppMessage) { print(message.id) }
}
Pushizo.InAppMessages.addLifecycleListener(logger) // zayıf referansla tutulurÇizim ayrı bir UIWindow üstünde UIKit görünümleriyle yapılır (SwiftUI ile uyumlu); dinamik yazı boyutu, açık/koyu tema, VoiceOver ve "Hareketi azalt" desteklenir. deeplink aksiyonu UIApplication.open ile açılır. Tetik kuralları (gruplar arası VEYA, grup içi VE; session_start, event, screen, trigger, session_duration), sıklık ve aksiyonlar (dismiss, url, deeplink, tags, push_prompt, outcome) tüm SDK'larda aynıdır; ortak vektörler sdk/testdata/messaging-vectors.json. Özel çizim: Pushizo.InAppMessages.setRenderer(HSInAppRenderer).
Bildirim kutusu
let page = try await Pushizo.Inbox.list(limit: 20, cursor: nil) // items, unreadCount, nextCursor, hasMore
let item = try await Pushizo.Inbox.markRead(page.items[0].id)
let remaining = try await Pushizo.Inbox.markAllRead()
try await Pushizo.Inbox.archive(item.id)
let unread = try await Pushizo.Inbox.unreadCount()
// Hatalar: HSError.notInitialized | .noSubscription | .noUser | .invalidArgument, HSApiError (ör. 403 identity_hash)Push aboneliği yoksa (ör. simülatör) in-app ve bildirim kutusu ilk e-posta/SMS aboneliğini kullanır.
Bildirim merkezi
Pushizo.Notifications.clearAll() // tüm teslim edilmiş bildirimler + rozet
Pushizo.Notifications.removeGroupedNotifications("orders") // aynı thread-id'li bildirimler
Pushizo.Notifications.removeNotification(identifier: id) // UNNotificationRequest.identifierthread_id, ios_relevance_score ve target_content_identifier sunucu tarafından aps içine yazılır ve sistem uygular. Aksiyon düğmeleri için UNNotificationCategory dinamik kaydedilir.
Davranışlar
- Bölgesel API kökü (ADR-0032 §4):
options.apiUrlvarsayılan bırakılırsa configapi_urlbenimsenir veUserDefaults'ta (App Group verildiyse NSE ile paylaşılır) saklanır; 421HS_WRONG_CELLyanıtındadetails.api_urlköküne geçilip istek bir kez yeniden denenir. Yalnızcahttpskabul edilir. - Token hex →
POST/PATCH /subscriptions; oturum:didBecomeActive→session_start,didEnterBackground→session_end(duration_seconds). - Çevrimdışı kuyruk
UserDefaults(App Group verilmişse paylaşımlı); NSE'ninreceivedkayıtları uygulama açılınca gönderilir.
Test
- Birim:
swift testya da Xcode (Tests/PushizoTests); macOS gerekir. - Cihaz:
examples/ios-swiftui/README.md. Simülatörde APNs kaydı yoktur;.apnsdosyası sürükleyerek gösterim/tıklama test edilir.
Sorun giderme
| Belirti | Neden / çözüm |
|---|---|
didFailToRegister | Push Notifications yeteneği yok veya simülatör. |
BadDeviceToken | Ortam uyumsuz (sandbox build ↔ production anahtarı). Dashboard'daki ortamı değiştirin. |
Unregistered | Uygulama silinmiş/token eski; abonelik otomatik invalid olur. |
| Görsel gelmiyor | NSE hedefi yok veya PushizoExtension eklenmemiş. |
| Yerel API'ye erişilemiyor | Gerçek cihazda LAN IP + Info.plist NSAppTransportSecurity → NSAllowsLocalNetworking. |
| In-app mesaj gelmiyor | Abonelik yok (push ya da e-posta/SMS), tetik sağlanmıyor, sıklık dolmuş ya da paused; .debug günlükte "in-app listesi yenilendi" satırına bakın. |
Inbox.list → HSError.noSubscription | Abonelik henüz kaydedilmedi (simülatörde önce addEmail ile e-posta aboneliği oluşturun). |