iosswiftnotificationsuilocalnotificationunnotificationattachment

When attaching thumbnails to notifications, where does the file go?


I am adding a thumbnail to a rich notification. I generate the image to be used as an attachment like this:

let url = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let fileURL = url.appendingPathComponent("someImageName", isDirectory: false).appendingPathExtension("png")
do{
    try UIImagePNGRepresentation(image)?.write(to: fileURL)
}catch{
    print("Could not write: ", error)
}

This is successful. If I now run this code:

let content = try? FileManager.default.contentsOfDirectory(atPath: NSTemporaryDirectory())
print(content ?? "error")

it prints out this: ["someImageName.png"].

Then I create the attachment, like this:

let attachment = try? UNNotificationAttachment(identifier: "image", url: url, options: nil)
let content = UNMutableNotificationContent()
content.title = "Test"
content.attachments = [attachment!]
let request = UNNotificationRequest(identifier: "someID", content:content, trigger: /*someTrigger*/)
UNUserNotificationCenter.current().add(request, withCompletionHandler:...)

This is all working. The notification gets sent, and the attachment is attached. But if I now print out the contents of my temporary folder, it is empty! The image I saved earlier has been removed from this folder.

After a lot of trial and error, I have found out that the image is removed at the very time I add the notification. Why is my notification deleting my image? I can't find any documentation on this!

If I do everything above, but comment out this line: //content.attachments = [attachment!], meaning that my stored image will not be added as an attachment to my notification, then my image is not being removed from the temporary folder!

Are these files being cleaned up after use? Or do I have to look in a different folder after the notification has disappeared and clean them up manually? I don't want my app to create hundreds of images that will never be used again, without being able to delete them..


Solution

  • I face the same issue these days and the answer is in the documentation, not very easy to see at first sight but it's there :

    About UNNotificationAttachment :

    ...Once validated, attached files are moved into the attachment data store so that they can be accessed by all of the appropriate processes.

    About the init method:

    When you schedule a notification request containing the attachment, the attachment's file is moved to a new location to facilitate access by the appropriate processes. After the move, the only way to access the file is using the methods of the UNUserNotificationCenter object.

    Notice that:

    Attachments located inside an app's bundle are copied instead of moved.

    So I decided to:

    Wrote the file like you did AND wrote a copy of it in a NSTemporaryDirectory() I used this example to easily create and delete TempDirectory (Swift 4.0) (Thanks to Ole Begemman):

    import Foundation
    
    /// A wrapper around a temporary file in a temporary directory. The directory
    /// has been especially created for the file, so it's safe to delete when you're
    /// done working with the file.
    ///
    /// Call `deleteDirectory` when you no longer need the file.
    struct TemporaryFile {
        let directoryURL: URL
        let fileURL: URL
        /// Deletes the temporary directory and all files in it.
        let deleteDirectory: () throws -> Void
        
        /// Creates a temporary directory with a unique name and initializes the
        /// receiver with a `fileURL` representing a file named `filename` in that
        /// directory.
        ///
        /// - Note: This doesn't create the file!
        init(creatingTempDirectoryForFilename filename: String) throws {
            let (directory, deleteDirectory) = try FileManager.default
                .urlForUniqueTemporaryDirectory()
            self.directoryURL = directory
            self.fileURL = directory.appendingPathComponent(filename)
            self.deleteDirectory = deleteDirectory
        }
    }
    
    extension FileManager {
        /// Creates a temporary directory with a unique name and returns its URL.
        ///
        /// - Returns: A tuple of the directory's URL and a delete function.
        ///   Call the function to delete the directory after you're done with it.
        ///
        /// - Note: You should not rely on the existence of the temporary directory
        ///   after the app is exited.
        func urlForUniqueTemporaryDirectory(preferredName: String? = nil) throws
            -> (url: URL, deleteDirectory: () throws -> Void)
        {
            let basename = preferredName ?? UUID().uuidString
            
            var counter = 0
            var createdSubdirectory: URL? = nil
            repeat {
                do {
                    let subdirName = counter == 0 ? basename : "\(basename)-\(counter)"
                    let subdirectory = temporaryDirectory
                        .appendingPathComponent(subdirName, isDirectory: true)
                    try createDirectory(at: subdirectory, withIntermediateDirectories: false)
                    createdSubdirectory = subdirectory
                } catch CocoaError.fileWriteFileExists {
                    // Catch file exists error and try again with another name.
                    // Other errors propagate to the caller.
                    counter += 1
                }
            } while createdSubdirectory == nil
            
            let directory = createdSubdirectory!
            let deleteDirectory: () throws -> Void = {
                try self.removeItem(at: directory)
            }
            return (directory, deleteDirectory)
        }
    }
    

    After attaching the file with the TempURL, the file is moved and you can delete the TempDirectory.

    Some tests later and to be sure, I finally found that my file (an image in my case) was store here :

    URL: file:///var/mobile/Library/SpringBoard/PushStore/Attachments/com.YourApp.Name/8e52c6673f6e735f83535486bf750ba1118a3ade.png
    

    After presenting or cleaning the notification center, the file is deleted yes.

    Why all this ? -> Sandboxing ! When I attached my file to a trigger (UNCalendarNotificationTrigger here for me) I named the Calendar responsible of my file to present it in my notification, but to handle it it has to have the file in HIS directory.

    Hope it helps, it took me a while to find all that mystery !