I 've successfully implemented push notification service for my ios app. I 've generated the required certificates and the code works. The problem arises when the device is disconnected from the internet and receives some notifications (which are pending and not displayed), then when the device is connected to the internet again ...only one of the pending apns push notifications are displayed. I am using php for my backend and NotificationServiceExtension for attachments etc.
Here is my php code
public static function sendAPNS($token,$data)
{
print_r($token);
$apnsServer = 'ssl://gateway.push.apple.com:2195';
$privateKeyPassword = 'password-here';
/* Device token */
$deviceToken = $token;
$pushCertAndKeyPemFile = 'nameofthefile.pem';
$stream = stream_context_create();
stream_context_set_option($stream, 'ssl', 'passphrase', $privateKeyPassword);
stream_context_set_option($stream, 'ssl', 'local_cert', $pushCertAndKeyPemFile);
$connectionTimeout = 20;
$connectionType = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT;
$connection = stream_socket_client($apnsServer, $errorNumber, $errorString, $connectionTimeout, $connectionType, $stream);
/*Alert array eg. body, title etc */
$alertArray = [];
$alertArray["body"] = $data["body"];
$alertArray["title"] =$data["title"];
$messageBody['aps'] = array(
'alert' => $alertArray,
'sound' => 'default',
'category'=> 'customUi',
'mutable-content'=>1
);
/*User Info*/
$messageBody["attachment-url"] = $data["url"];
$messageBody["type_code"] = $data["type_code"];
$messageBody["ref_id"] = $data["ref_id"];
$messageBody["user_to"] =$data["user_to"];
/*Could be here*/
$payload = json_encode($messageBody);
print_r($payload);
$notification = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
$wroteSuccessfully = fwrite($connection, $notification, strlen($notification));
fclose($connection);
}
My Service extension is as follows :-
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
var defaultsUser: UserDefaults = UserDefaults(suiteName: "group.shared.com.plates215")!
if let countNot = defaultsUser.value(forKey: "notUniversal")
{
var IntcountNot = countNot as! Int
IntcountNot = IntcountNot + 1
var sum: NSNumber = NSNumber(value: IntcountNot)
bestAttemptContent.badge = sum
defaultsUser.set(IntcountNot, forKey: "notUniversal")
}
if let photo = bestAttemptContent.userInfo["attachment-url"] as? String
{
updateReadNots(userID: (bestAttemptContent.userInfo["user_to"] as? String)!)
let url = NSURL(string: photo);
var err: NSError?
var imageData :NSData = try! NSData(contentsOf: url! as URL)
var bgImage = UIImage(data:imageData as Data)
if let attachment = UNNotificationAttachment.create(identifier: "colo", image: bgImage!, options: nil)
{
bestAttemptContent.attachments = [attachment]
contentHandler(bestAttemptContent)
}
}
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
func updateReadNots(userID: String)
{
var url: String = "api-url"
let session: URLSession = URLSession(configuration: URLSessionConfiguration.default)
var urlDown = URL(string: url)
let downloadTask = session.downloadTask(with: urlDown!) { (url, rsp, error) in
}
downloadTask.resume()
}
}
extension UNNotificationAttachment {
static func create(identifier: String, image: UIImage, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
let tmpSubFolderURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
do {
try fileManager.createDirectory(at: tmpSubFolderURL, withIntermediateDirectories: true, attributes: nil)
let imageFileIdentifier = identifier+".png"
let fileURL = tmpSubFolderURL.appendingPathComponent(imageFileIdentifier)
guard let imageData = UIImagePNGRepresentation(image) else {
return nil
}
try imageData.write(to: fileURL)
let imageAttachment = try UNNotificationAttachment.init(identifier: imageFileIdentifier, url: fileURL, options: options)
return imageAttachment
} catch {
print("error " + error.localizedDescription)
}
return nil
}
}
SOLUTION
As per @Li Sim 's link, this is what i did ...every notification that i
send from my server contains not only the latest notification, but also
every single unread notification seperated by a "\n". And then ios
notifications properly display them. To keep a track of read and unread
notifications, i have a mantained a read_status key in my back_end which
is updated as soon as the notification is recieved in the
NotificationContentExtension
(https://developer.apple.com/documentation/usernotifications/unnotificatio nserviceextension). It is used to modify content and execute some code
whenever an apns push is recieved. Hope this helps someone in the future
:)