Tüm Yazılar
KategoriNotifications
Okuma Süresi
21 dk
Yayın Tarihi
...
Kelime Sayısı
1.966kelime

Kahveni hazırla - bu içerikli bir makale!

iOS push notification'larının tüm gücünü keşfedin. Rich media, actionable notifications, notification service extension ve silent push stratejileri.

Advanced Push Notifications: APNs'ten Rich Media'ya Her Şey

Push notification, uygulamanın kullanıcıyla konuşabildiği en güçlü kanaldır. Ama çoğu geliştirici sadece yüzeyini kullanıyor. Bu rehberde, notification'ların tüm potansiyelini açığa çıkaracağız.

İçindekiler

  1. APNs Temelleri
  2. Push Notification Kurulumu
  3. Payload Yapısı ve Özelleştirme
  4. Rich Notifications
  5. Notification Actions
  6. Notification Service Extension
  7. Notification Content Extension
  8. Silent Push ve Background Refresh
  9. Provisional ve Critical Alerts
  10. Analytics ve Best Practices
  11. Sonuç ve Öneriler

APNs Temelleri {#apns-temelleri}

Apple Push Notification service (APNs), tüm Apple cihazlarına notification gönderen merkezi sistemdir.

Nasıl Çalışır?

Server'ınız APNs'e push isteği gönderir, APNs bunu kullanıcının cihazına iletir.

APNs Endpoints

Ortam
URL
**Development**
api.sandbox.push.apple.com:443
**Production**
api.push.apple.com:443

Authentication Yöntemleri

1. Token-Based (Önerilen - .p8 key)

  • Tek key tüm app'ler için
  • Token 1 saat geçerli
  • Sertifika yenileme yok

2. Certificate-Based (.p12)

  • Her app için ayrı sertifika
  • Yıllık yenileme gerekli
  • Eski yöntem

Push Notification Kurulumu {#push-kurulum}

1. App Configuration

AppDelegate'de notification kurulumu adım adım:

swift
1import UIKit
2import UserNotifications
3 
4@main
5class AppDelegate: UIResponder, UIApplicationDelegate {
6 
7 func application(
8 _ application: UIApplication,
9 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
10 ) -> Bool {
11 // 1. Delegate'i ayarla
12 UNUserNotificationCenter.current().delegate = self
13 
14 // 2. İzin iste
15 requestNotificationPermission()
16 
17 // 3. Remote notification'a kayıt ol
18 application.registerForRemoteNotifications()
19 
20 return true
21 }
22 
23 private func requestNotificationPermission() {
24 let options: UNAuthorizationOptions = [.alert, .badge, .sound, .provisional]
25 UNUserNotificationCenter.current().requestAuthorization(options: options) { granted, error in
26 if let error = error {
27 print("Permission error: \(error.localizedDescription)")
28 return
29 }
30 print("Permission granted: \(granted)")
31 }
32 }
33 
34 // 4. Device token'ı al
35 func application(
36 _ application: UIApplication,
37 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
38 ) {
39 let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
40 print("Device Token: \(token)")
41 // Backend'e gönder
42 APIService.shared.registerDeviceToken(token)
43 }
44 
45 func application(
46 _ application: UIApplication,
47 didFailToRegisterForRemoteNotificationsWithError error: Error
48 ) {
49 print("Failed to register: \(error.localizedDescription)")
50 }
51}
52 
53// MARK: - UNUserNotificationCenterDelegate
54extension AppDelegate: UNUserNotificationCenterDelegate {
55 
56 // Foreground'da notification geldiğinde
57 func userNotificationCenter(
58 _ center: UNUserNotificationCenter,
59 willPresent notification: UNNotification
60 ) async -> UNNotificationPresentationOptions {
61 return [.banner, .badge, .sound]
62 }
63 
64 // Notification'a tıklandığında
65 func userNotificationCenter(
66 _ center: UNUserNotificationCenter,
67 didReceive response: UNNotificationResponse
68 ) async {
69 let userInfo = response.notification.request.content.userInfo
70 handleNotificationAction(response: response, userInfo: userInfo)
71 }
72 
73 private func handleNotificationAction(
74 response: UNNotificationResponse,
75 userInfo: [AnyHashable: Any]
76 ) {
77 switch response.actionIdentifier {
78 case "REPLY_ACTION":
79 if let textResponse = response as? UNTextInputNotificationResponse {
80 print("User replied: \(textResponse.userText)")
81 }
82 case "LIKE_ACTION":
83 print("User liked the message")
84 case UNNotificationDefaultActionIdentifier:
85 // Notification'a direkt tıklama — deep link'e yönlendir
86 if let deepLink = userInfo["deep_link"] as? String {
87 DeepLinkRouter.shared.navigate(to: deepLink)
88 }
89 default:
90 break
91 }
92 }
93}

2. Permission Request Stratejisi

swift
1// Akıllı izin isteme — kullanıcıya ÖNCE değeri göster, SONRA izin iste
2final class NotificationPermissionManager {
3 
4 enum PermissionStatus {
5 case notDetermined, authorized, denied, provisional
6 }
7 
8 static let shared = NotificationPermissionManager()
9 
10 func checkCurrentStatus() async -> PermissionStatus {
11 let settings = await UNUserNotificationCenter.current().notificationSettings()
12 switch settings.authorizationStatus {
13 case .notDetermined: return .notDetermined
14 case .authorized: return .authorized
15 case .denied: return .denied
16 case .provisional: return .provisional
17 @unknown default: return .notDetermined
18 }
19 }
20 
21 /// Soft-ask: Önce kendi UI'ımızla sor, kabul ederse sistem dialog'unu göster
22 func requestPermissionWithPrePrompt(from viewController: UIViewController) async -> Bool {
23 let status = await checkCurrentStatus()
24 
25 switch status {
26 case .authorized:
27 return true
28 case .denied:
29 // Ayarlara yönlendir
30 showSettingsAlert(from: viewController)
31 return false
32 case .notDetermined, .provisional:
33 // Sistem dialog'unu göster
34 do {
35 let granted = try await UNUserNotificationCenter.current()
36 .requestAuthorization(options: [.alert, .badge, .sound])
37 if granted {
38 await MainActor.run {
39 UIApplication.shared.registerForRemoteNotifications()
40 }
41 }
42 return granted
43 } catch {
44 return false
45 }
46 }
47 }
48 
49 private func showSettingsAlert(from vc: UIViewController) {
50 let alert = UIAlertController(
51 title: "Bildirimler Kapalı",
52 message: "Bildirimleri almak için Ayarlar'dan izin vermeniz gerekiyor.",
53 preferredStyle: .alert
54 )
55 alert.addAction(UIAlertAction(title: "Ayarlar", style: .default) { _ in
56 if let url = URL(string: UIApplication.openSettingsURLString) {
57 UIApplication.shared.open(url)
58 }
59 })
60 alert.addAction(UIAlertAction(title: "Vazgeç", style: .cancel))
61 vc.present(alert, animated: true)
62 }
63}

3. Device Token Yönetimi

Device token her zaman değişebilir. Güvenilir token yönetimi şart:

swift
1final class DeviceTokenManager {
2 private static let tokenKey = "stored_device_token"
3 
4 /// Token değiştiyse backend'e gönder
5 static func handleNewToken(_ tokenData: Data) {
6 let newToken = tokenData.map { String(format: "%02.2hhx", $0) }.joined()
7 let oldToken = UserDefaults.standard.string(forKey: tokenKey)
8 
9 guard newToken != oldToken else { return } // Aynıysa skip
10 
11 UserDefaults.standard.set(newToken, forKey: tokenKey)
12 Task {
13 try await APIService.shared.updateDeviceToken(
14 oldToken: oldToken,
15 newToken: newToken
16 )
17 }
18 }
19 
20 /// Token invalidation — 410 response geldiğinde çağır
21 static func invalidateToken() {
22 UserDefaults.standard.removeObject(forKey: tokenKey)
23 }
24}

Payload Yapısı ve Özelleştirme {#payload-yapisi}

APNs payload'u JSON formatındadır ve "aps" anahtarı altında Apple'ın standart alanlarını içerir.

Temel Payload Yapısı

  • alert: title, subtitle, body içerir
  • badge: App icon badge sayısı
  • sound: Ses dosyası adı veya "default"
  • thread-id: Notification grouping
  • category: Action category ID
  • content-available: Silent push için 1
  • mutable-content: Service extension için 1
  • interruption-level: passive/active/time-sensitive/critical
  • relevance-score: 0-1 arası öncelik

Custom Data

"aps" dışındaki alanlar custom data olarak kullanılabilir. Örneğin chat_id, sender_id gibi uygulama spesifik veriler.


Rich Notifications {#rich-notifications}

iOS 10+ ile notification'lara medya eklenebilir.

Notification Service Extension

Service extension ile gelen notification'ı modifiye edebilirsin:

  • Media indirme (resim, video, ses)
  • İçerik şifre çözme
  • Lokalizasyon

Önemli: 30 saniye timeout var. serviceExtensionTimeWillExpire çağrılmadan işlemi tamamla.

Desteklenen Media Türleri

Tür
Max Boyut
Formatlar
**Image**
10 MB
JPEG, GIF, PNG
**Audio**
5 MB
MP3, WAV, M4A
**Video**
50 MB
MPEG, MP4, AVI

Notification Actions {#notification-actions}

Notification'lara interaktif butonlar ekle.

Action Türleri

  1. UNNotificationAction: Basit buton
  2. UNTextInputNotificationAction: Metin girişli buton
  3. Destructive: Kırmızı, tehlikeli aksiyonlar için
  4. Foreground: App'i açan aksiyonlar

Category ve Action Tanımlama

swift
1// AppDelegate veya SceneDelegate'de category'leri kaydet
2func registerNotificationCategories() {
3 // Reply action — metin girişli
4 let replyAction = UNTextInputNotificationAction(
5 identifier: "REPLY_ACTION",
6 title: "Yanıtla",
7 options: [],
8 textInputButtonTitle: "Gönder",
9 textInputPlaceholder: "Mesajınızı yazın..."
10 )
11 
12 // Like action — basit buton
13 let likeAction = UNNotificationAction(
14 identifier: "LIKE_ACTION",
15 title: "Beğen",
16 options: []
17 )
18 
19 // Delete action — destructive (kırmızı)
20 let deleteAction = UNNotificationAction(
21 identifier: "DELETE_ACTION",
22 title: "Sil",
23 options: [.destructive, .authenticationRequired]
24 )
25 
26 // Open action — app'i açar
27 let openAction = UNNotificationAction(
28 identifier: "OPEN_ACTION",
29 title: "Görüntüle",
30 options: [.foreground]
31 )
32 
33 // Message category
34 let messageCategory = UNNotificationCategory(
35 identifier: "MESSAGE_CATEGORY",
36 actions: [replyAction, likeAction, deleteAction],
37 intentIdentifiers: [],
38 options: [.customDismissAction]
39 )
40 
41 // Promo category
42 let promoCategory = UNNotificationCategory(
43 identifier: "PROMO_CATEGORY",
44 actions: [openAction],
45 intentIdentifiers: [],
46 options: []
47 )
48 
49 UNUserNotificationCenter.current().setNotificationCategories([
50 messageCategory,
51 promoCategory
52 ])
53}

Payload'da "category" alanıyla eşleştir:

json
1{
2 "aps": {
3 "alert": {
4 "title": "Ahmet",
5 "body": "Merhaba, nasılsın?"
6 },
7 "category": "MESSAGE_CATEGORY",
8 "mutable-content": 1
9 },
10 "sender_id": "user_123",
11 "chat_id": "chat_456"
12}

Notification Service Extension {#service-extension}

Service extension özellikleri:

  • 30 saniye timeout
  • Media download
  • Content modification
  • End-to-end encryption için decryption

Notification Service Extension Implementasyonu

swift
1import UserNotifications
2 
3class NotificationService: UNNotificationServiceExtension {
4 
5 var contentHandler: ((UNNotificationContent) -> Void)?
6 var bestAttemptContent: UNMutableNotificationContent?
7 
8 override func didReceive(
9 _ request: UNNotificationRequest,
10 withContentHandler contentHandler: @escaping(UNNotificationContent) -> Void
11 ) {
12 self.contentHandler = contentHandler
13 bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent
14 
15 guard let bestAttemptContent = bestAttemptContent else {
16 contentHandler(request.content)
17 return
18 }
19 
20 // Media URL varsa indir ve ekle
21 if let mediaURLString = bestAttemptContent.userInfo["media_url"] as? String,
22 let mediaURL = URL(string: mediaURLString) {
23 downloadMedia(from: mediaURL) { attachment in
24 if let attachment = attachment {
25 bestAttemptContent.attachments = [attachment]
26 }
27 contentHandler(bestAttemptContent)
28 }
29 } else {
30 contentHandler(bestAttemptContent)
31 }
32 }
33 
34 private func downloadMedia(
35 from url: URL,
36 completion: @escaping(UNNotificationAttachment?) -> Void
37 ) {
38 let task = URLSession.shared.downloadTask(with: url) { localURL, response, error in
39 guard let localURL = localURL, error == nil else {
40 completion(nil)
41 return
42 }
43 
44 // Geçici dosyaya kaydet (doğru extension ile)
45 let fileExtension = url.pathExtension.isEmpty ? "jpg" : url.pathExtension
46 let tempDir = FileManager.default.temporaryDirectory
47 let tempFile = tempDir.appendingPathComponent(UUID().uuidString + ".\(fileExtension)")
48 
49 do {
50 try FileManager.default.moveItem(at: localURL, to: tempFile)
51 let attachment = try UNNotificationAttachment(
52 identifier: "media",
53 url: tempFile,
54 options: nil
55 )
56 completion(attachment)
57 } catch {
58 completion(nil)
59 }
60 }
61 task.resume()
62 }
63 
64 // 30 saniye dolduğunda çağrılır — son şansın
65 override func serviceExtensionTimeWillExpire() {
66 if let contentHandler = contentHandler,
67 let bestAttemptContent = bestAttemptContent {
68 bestAttemptContent.title += " [Media yüklenemedi]"
69 contentHandler(bestAttemptContent)
70 }
71 }
72}

Best Practices

  1. Hızlı çalış - timeout'a dikkat
  2. Media download için URLSession kullan
  3. Fallback senaryosu hazırla
  4. Error handling ekle

Notification Content Extension {#content-extension}

Custom notification UI oluştur.

Info.plist Ayarları

  • UNNotificationExtensionCategory: Hangi category'ler için
  • UNNotificationExtensionDefaultContentHidden: Varsayılan içeriği gizle
  • UNNotificationExtensionInitialContentSizeRatio: Başlangıç boyutu

Interactive UI

  • UIViewController subclass'ı
  • UNNotificationContentExtension protokolü
  • didReceive ile içeriği al
  • Custom view'ler ile göster

Silent Push ve Background Refresh {#silent-push}

Kullanıcıyı rahatsız etmeden data sync.

Silent Push Payload

content-available: 1 ile silent push gönder. UI göstermeden arka planda işlem yap.

Background Fetch

didReceiveRemoteNotification ile silent push'ı yakala ve UIBackgroundFetchResult ile sonucu bildir:

  • .newData - Yeni veri var
  • .noData - Yeni veri yok
  • .failed - Hata oluştu
⚠️ Dikkat: Silent push'lar throttle edilebilir. Apple sık silent push gönderimini tespit ederse delivery düşer!

Provisional ve Critical Alerts {#ozel-tipler}

Provisional Authorization (iOS 12+)

Kullanıcıya sormadan "quiet" notification göster. Notification Center'da görünür ama ses/banner yok. Kullanıcı isterse kalıcı izin verebilir.

Critical Alerts

Sessiz modda bile ses çıkarır. Sağlık, güvenlik uygulamaları için Apple'dan özel entitlement gerekli.

Time Sensitive (iOS 15+)

Focus mode'u aşabilir. interruption-level: "time-sensitive" ile gönder.


Analytics ve Best Practices {#analytics}

Notification Tracking

  1. Received - Service extension'da track et
  2. Displayed - willPresent delegate'inde
  3. Clicked - didReceive response'da
  4. Action - Hangi action'a tıklandı

APNs Response Codes

  • 200: Success
  • 400: Bad request
  • 403: Certificate/token issue
  • 404: Bad device token
  • 410: Unregistered device (token'ı sil!)
  • 429: Too many requests
  • 500: APNs server error

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?


Okuyucu Ödülü

Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:

ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

Sonuç ve Öneriler {#sonuc-ve-oneriler}

Push notification, doğru kullanıldığında güçlü bir araç. İşte özet:

  1. Rich media kullan - Görsel notification'lar %40 daha fazla engagement
  2. Action ekle - Kullanıcı app'i açmadan işlem yapabilsin
  3. Silent push - Background sync için ideal
  4. Analytics - Her notification'ı track et
  5. Spam yapma - Günlük 3-5 notification maksimum

Kaynaklar


*Notification spam değil, değer sunmalı. Her push, kullanıcıya fayda sağlamalı.* 🔔

Etiketler

#Push Notifications#APNs#iOS#UserNotifications#Rich Media#Swift
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz