Save and Retrieve Image File From Realm - ios

Writing to the file system allows users to write and retrieve string, image, etc., files to and from the application on their devices.
The images I write and retrieve to the app that appear when I run the simulator, for example, are unique to that app bundle, and differ from the images I write and retrieve from my device.
When I research how to write and read image files with Realm I am told not to save the image but the path. Images are too large to store. Got it.
But the file path I am saving naturally returns data native to the specific app bundle-- or returns nil. How might I write and retrieve image files/paths to and from Realm if the file path is unique to the device(s)? Do I need a separate image array?
First View Controller
let planet = planets[indexPath.item]
cell.name.text = planet.name
cell.system.text = planet.system
let imageData = (self.getDirectoryPath() as NSString).appendingPathComponent("image")
if self.fileManager.fileExists(atPath: imageData) {
// Populates every cell with the same image
cell.earthImage.image = UIImage(contentsOfFile: imageData)
// returns nil
cell.earthImage.image = UIImage(contentsOfFile: planet.image)
}
// Retrieve directory path for image
func getDirectoryPath() -> String {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
return documentsDirectory
}
Second View Controller
let planet = Planet()
self.realm = try! Realm(configuration: config(user: SyncUser.current!))
planet.name = self.name.text!
planet.system = self.system.text!
let imageData = (self.getDirectoryPath() as NSString).appendingPathComponent("person")
if self.fileManager.fileExists(atPath: imageData) {
self.earthImage.image = UIImage(contentsOfFile: imageData)
// Why does this not write to realm?
planet.image = imageData
}
try! self.realm.write {
self.realm.add(planet)
}
Here, I am posting from the secondViewController where I would like to render these values in my collectionView in the firstViewController's numberOfItemsInSection_:) method.
Here's the model:
class Planet: Object {
#objc dynamic var name = String()
#objc dynamic var system = String()
#objc dynamic var image = String()
}

Related

Handling image load/store in a UIDocument that contains multiple images

I have a simple model object Location with a few text items and a images : [UIImages]?. Location is Codable so I store the text bits as JSON and then write the images into the same FileWrapper.
My question is how to store the relationship between the image files and the [UIImage] array. The images have to come back in the same order. Is there a way I can hook into Coding so that the array gets replaced by the URLs pointing to the images?
Or alternately, should I always have the images as separate files, say in the cache directory, and replace the [UIImage] with [URL]s
Here's an example of storing a bunch of image files in a file wrapper, along with an "index" (which I call list) of their names in a specific order:
let fm = FileManager.default
let docurl = fm.urls(for: .documentDirectory, in: .userDomainMask)[0]
let d = FileWrapper(directoryWithFileWrappers: [:])
let imnames = ["manny.jpg", "moe.jpg", "jack.jpg"]
for imname in imnames {
let im = UIImage(named:imname)!
let imfw = FileWrapper(regularFileWithContents: UIImageJPEGRepresentation(im, 1)!)
imfw.preferredFilename = imname
d.addFileWrapper(imfw)
}
let list = try! JSONEncoder().encode(imnames)
let listfw = FileWrapper(regularFileWithContents: list)
listfw.preferredFilename = "list"
d.addFileWrapper(listfw)
do {
try d.write(to: docurl.appendingPathComponent("myFileWrapper"), originalContentsURL: nil)
print("ok")
} catch {
print(error)
}
And here's an example of reading that file wrapper by fetching the list and then getting the image files by name in that same order:
let fm = FileManager.default
let docurl = fm.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fwurl = docurl.appendingPathComponent("myFileWrapper")
do {
let d = try FileWrapper(url: fwurl)
if let list = d.fileWrappers?["list"]?.regularFileContents {
let imnames = try! JSONDecoder().decode([String].self, from: list)
for imname in imnames {
if let imdata = d.fileWrappers?[imname]?.regularFileContents {
print("got image data for", imname)
// in real life, do something with the image here
}
}
} else {
print("no list")
}
} catch {
print(error); return
}
That prints:
got image data for manny.jpg
got image data for moe.jpg
got image data for jack.jpg
All you want to do in UIDocument is that same thing, except that UIDocument will write and read the file wrapper for you.

Cannot read coredata from extension

I have an app that stores some information in coredata and reads them.
I'm writing a message extension of this application and I'd like to have this extension reading the same data but I always have empty response.
Here is the code I'm using in the main app:
context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
fetchImages(Date()){ (array, arrayData) in
for image in array{
imagesArray.insert(image, at:0)
}
}
I'm using exactly the same code in the extension but it does not read the data.
What I'm wondering about is that I'm not using the appGroupIdentifier anywhere in the code.
How can I do to achieve that?
Thanks.
Here is the code of fetchImages function:
func fetchImages(_ predicate:Date, completion:(_ array:[Image], _ arrayData:NSArray)->()){
var arrData = [NSManagedObject]()
var existingImages = [Image]()
let request :NSFetchrequest<NSFetchrequestResult> = NSFetchrequest(entityName: "Photo")
do {
let results = try context?.fetch(request)
var myImage = Image()
if ((results?.count) != nil) {
for result in results! {
myImage.imageUrl = (resultat as! NSManagedObject).value(forKey:"url") as! String
myImage.imageFileName = (resultat as! NSManagedObject).value(forKey:"imageFileName") as! String
existingImages.append(myImage)
arrData.append(result as! NSManagedObject)
}
} else{
print ("No photo.")
}
completion(existingImages, arrData as NSArray)
} catch{
print ("Error during CoreData request")
}
}
Turning on app groups is the first step, but now you need to tell Core Data to use the app group.
First you get the location of the shared group container, from FileManager. Use containerURL(forSecurityApplicationGroupIdentifier:) to get a file URL for the directory.
You can use that URL without changes if you want. It's probably a good idea to create a subdirectory in it to hold your Core Data files. If you do that, add a directory name to the URL with the appendingPathComponent() method on URL. Then use FileManager to create the new directory with the createDirectory(at:withIntermediateDirectories:attributes:) method.
Now that you have a shared directory to use, tell NSPersistentContainer to put its files there. You do that by using NSPersistentStoreDescription. The initializer can take a URL that tells it where to store its data.
Your code will be something approximating this:
let directory: URL = // URL for your shared directory as described above
let containerName: String = // Your persistent container name
let persistentContainer = NSPersistentContainer(name: containerName)
let persistentStoreDirectoryUrl = directory.appendingPathComponent(containerName)
guard let _ = try? FileManager.default.createDirectory(at: persistentStoreDirectoryUrl, withIntermediateDirectories: true, attributes: nil) else {
fatalError()
}
let persistentStoreUrl = persistentStoreDirectoryUrl.appendingPathComponent("\(containerName).sqlite")
let persistentStoreDescription = NSPersistentStoreDescription(url: persistentStoreUrl)
persistentContainer.persistentStoreDescriptions = [ persistentStoreDescription ]
persistentContainer.loadPersistentStores {
...
}

Swift 3 - Save image alternative

I need to found an alternative of this method to save images
let save = UserDefaults.standard
let imageData = UIImageJPEGRepresentation(Photo.image!, 1.0)
save.set(imageData, forKey: "Image")
save.synchronize()
if let imgData = save.object(forKey: "Image"){
let compressedJPGImage = UIImage(data: imgData as! Data)
}
and load images
let imgData = save.object(forKey: "Image")
let compressedJPGImage = UIImage(data: imgData as! Data)
Photo.image = compressedJPGImage
The problem with this method is that i have a lot of another value saved with UserDefaults.standard so it take a lot of time (5-10 minutes) when i synchronize.
It is not advisable to save large files like images to UserDefaults. UserDefaults was intended to save very small data such as a user's preferred theme color of your app. Perhaps a suitable alternative is to save your images in the document directory. Here is a function that will allow you save an image:
func saveImage(image: UIImage) -> String {
let imageData = NSData(data: UIImagePNGRepresentation(image)!)
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let docs = paths[0] as NSString
let uuid = NSUUID().uuidString + ".png"
let fullPath = docs.appendingPathComponent(uuid)
_ = imageData.write(toFile: fullPath, atomically: true)
return uuid
}
The above function will create the name of the saved image for you. If you prefer to specify the name of the image you are saving then you could do the following (but you will be responsible for ensuring the image names you specify are unique):
func saveImage(image: UIImage, withName name: String) {
let imageData = NSData(data: UIImagePNGRepresentation(image)!)
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let docs = paths[0] as NSString
let name = name
let fullPath = docs.appendingPathComponent(name)
_ = imageData.write(toFile: fullPath, atomically: true)
}
To retrieve those images, you could pass the image name to this function:
func getImage(imageName: String) -> UIImage? {
var savedImage: UIImage?
if let imagePath = getFilePath(fileName: imageName) {
savedImage = UIImage(contentsOfFile: imagePath)
}
else {
savedImage = nil
}
return savedImage
}
Which relies on this function to work:
func getFilePath(fileName: String) -> String? {
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
var filePath: String?
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
if paths.count > 0 {
let dirPath = paths[0] as NSString
filePath = dirPath.appendingPathComponent(fileName)
}
else {
filePath = nil
}
return filePath
}
Here is an example of how you would now save your images instead of UserDefaults. I am saving an image I will call "Image":
saveImage(image: Photo.image, withName name: "Image")
Here is an example of how I would retrieve the saved image:
if let theSavedImage = getImage(imageName: "Image") {
//I got the image
}
UserDefaults is a place to store a small portion of data like user preferences. UserDefaults has very limited space and can be quite slow. In your case, you mentioned it's 5-10 minutes which I doubt though.
If you want images to be stored across the sessions of the app (persistent storage), you should consider using file system (Application_Folder/Library/Cache/) or Core Data framework. You will get better performance here while accessing the image.
If images are not needed to be persisted and need to be stored for a single session of the app, you should use the imageNamed: API of UIImage class. This API loads the image once in the memory and keeps it in the system cache. For all the successive accesses it refers to the cached image only. This will increase the system cache size and application's memory footprint if you are loading too many images. Another API is imageWithContentsOfFile:. Unlike the first API, this API will always load the new image instance in memory. Memory will be deallocated once image instance is released which is not the case with the first API.

Problems with loading image path from realm in Swift

In my app I am storing an image in local storage and I am saving the path of that image in my Realm database. And now i have problems with load this image from that path?
Thats how I save path to database:
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory,nsUserDomainMask, true)
let dirPath = paths.first
let imageURL1 = URL(fileURLWithPath: dirPath!).appendingPathComponent("refuge1.jpg")
let med1 = Meditation()
med1.name = "Refuge"
med1.count = 0
med1.targetAmount = 111111
med1.malasStep = 108
med1.imagePath = imageURL1.path
med1.id = 1
It's straightforward that am trying to get an image from this meditation.imagePath path. I double-checked the path, image is there still am not able to set the image using this path, is there is something that am missing?
In debug mode I see this:
Meditation {
name = Refuge;
count = 0;
targetAmount = 111111;
malasStep = 108;
imagePath = /Users/macbook/Library/Developer/CoreSimulator/Devices/2E25309F-D6A9-41C3-9EF4-67203142172C/data/Containers/Data/Application/F198640B-3C72-4F9C-8173-FB00D3ABEC15/Documents/refuge1.jpg;
id = 1;}
but my variable image still nil in debug mode
// Configure the cell...
cell.nameOfMeditation.text = meditation.name
cell.countOfMeditation.text = String(meditation.count)
let image = UIImage(contentsOfFile: meditation.imagePath)
cell.imageForMeditation.image = image
return cell
I see name of meditation and sound, bot no omg.
It's not advised to save the absolute file path of a file in an iOS app (ie, everything including /Users/macbook/Library/Developer/...), in Realm or anywhere else.
For security reasons, iOS devices rename the UUID folder name between launches. This means that while the folder path was valid at the time it was saved, it won't be at a later date.
Instead, it's recommended to save just the relative path of the file (eg, its location in relation to just the Documents folder. In this case, it would be just /refuge1.jpg) and to then dynamically build the absolute path by requesting the Documents directory path as you need it.
Try this:
// Use these convenience methods if you do a lot of saving and loading
func getDocumentsURL() -> URL {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
return documentsURL
}
func fileInDocumentsDirectory(_ filename: String) -> String {
let fileURL = getDocumentsURL().appendingPathComponent(filename)
return fileURL.path
}
func saveRefugeOne(image: UIImage) {
// Create a file name, and then save the path in your documents with that name
let imageFileName:String = "refuge1.jpg"
let imagePath = fileInDocumentsDirectory(imageFileName!)
saveImage(image, path: imagePath)
}
func loadRefugeOne() -> UIImage? {
// Get the image back
let imageName:String = "refuge1.jpg" // Or whatever name you saved
let imagePath = fileInDocumentsDirectory(imageName)
if let loadedImage = self.loadImageFromPath(imagePath) {
return loadedImage
} else {
print("Couldn't Load: \(imageName)")
return nil
}
}
// This will be your method to save image
func saveImage(_ image: UIImage, path: String ) {
//If you want PNG use this let pngImageData = UIImagePNGRepresentation(image)
// But since you mentioned JPEG:
if let jpgData = UIImageJPEGRepresentation(image, 1.0) {
try? jpgData.write(to: URL(fileURLWithPath: path), options: [.atomic])
}
}
// This will load image from saved path. Make sure to store the path
// somewhere. This makes it easier to save images locally. You can
// save the image in the documents directory, and then just save the
// path in CoreData or something similar.
func loadImageFromPath(_ path: String) -> UIImage? {
let image = UIImage(contentsOfFile: path)
if image == nil {
print("couldn't find image at path: \(path)")
}
return image
}
Hopefully this will help. It's the method I always use, and it works like a charm when I follow my own steps right ;-)

How to overwrite/delete saved images in app

I am developing an app where user can save 13 screenshots and display them on a single view as thumbnails or as a full screen image. This is how I save the screenshots:
let fileName:String = self.stickerUsed + "saved" + ".png"
var arrayPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! NSString
var pngFileName = arrayPaths.stringByAppendingPathComponent(fileName)
UIImagePNGRepresentation(resizeImage(screenshot!, newSize: CGSizeMake(self.view.frame.width, self.view.frame.height))).writeToFile(pngFileName, atomically:true)
NSUserDefaults.standardUserDefaults().setObject(pngFileName, forKey: self.stickerUsed)
NSUserDefaults.standardUserDefaults().synchronize()
and this is how i retrieve them:
var defaultName:String = self.stickerUsed + "saved" + ".png"
let path = NSSearchPathForDirectoriesInDomains(
.DocumentDirectory, .UserDomainMask, true)[0] as! NSString
let fileName = NSUserDefaults.standardUserDefaults()
.stringForKey(stickerUsed) ?? defaultName
let imagePath = path.stringByAppendingPathComponent(fileName)
let image = UIImage(named: imagePath )
How do i overwrite the saved images ? When i use the code to save imageof a different screenshot with same filename and then retrieve it i get the previous image that was initially saved and new image is not overwritten!
I made your code cleaner and fixed the problem! Comments will help you learn some basic stuff about how it works.
First, define function that gives you full filepath from filename:
func documentsPathWithFileName(fileName : String) -> String {
// Get file path to document directory root
let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true)[0] as! String
return documentsDirectoryPath.stringByAppendingPathComponent(fileName)
}
And then, function that takes UIImage and saves it, or replaces it, depending:
func saveOverrideImage(screenshot : UIImage, stickerName : String) {
// Get file path poining to documents directory
let filePath = documentsPathWithFileName(stickerName)
// We could try if there is file in this path (.fileExistsAtPath())
// BUT we can also just call delete function, because it checks by itself
NSFileManager.defaultManager().removeItemAtPath(filePath, error: nil)
// Resize image as you want
let image : NSData = UIImagePNGRepresentation(resizeImage(screenshot!, newSize: CGSizeMake(self.view.frame.width, self.view.frame.height)))
// Write new image
image.writeToFile(filePath, atomically: true)
// Save your stuff to
NSUserDefaults.standardUserDefaults().setObject(stickerName, forKey: self.stickerUsed)
NSUserDefaults.standardUserDefaults().synchronize()
}
Also, following the same style, you can get image:
func imageForStickerName(stickerName : String) -> UIImage? {
// Get file path poining to documents directory
let filePath = documentsPathWithFileName(stickerName)
// Get image from file on given local url
return UIImage(contentsOfFile: filePath)
}
Hope it helps!
Try You can use
var error:NSError?
var resultingURL:NSURL?
let oldURL:NSURL = NSURL.fileURLWithPath("<old path>")!
let newURL:NSURL = NSURL.fileURLWithPath("<new path>")!
fileManager.replaceItemAtURL(oldURL,
withItemAtURL: newURL,
backupItemName: nil,
options: .UsingNewMetadataOnly,
resultingItemURL: &resultingURL,
error: &error)
or you can delete old file and write new on, for more details on FileManager visit here

Resources