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

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.

Related

How do I change the value stored in a image file in swift 4

I save my first image and it loads, but when I save the second, the image of the button stays the same as the first image I saved. Can someone show me how to have that files value be able to change?
Here is the code that runs when someone picks an image:
if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// choose a name for your image
let fileName = "image.jpg"
// create the destination file url to save your image
let fileURL = documentsDirectory.appendingPathComponent(fileName)
// get your UIImage jpeg data representation and check if the destination file url already exists
if let data = UIImageJPEGRepresentation(pickedImage, 1.0),
!FileManager.default.fileExists(atPath: fileURL.path) {
do {
// writes the image data to disk
try data.write(to: fileURL)
print("file saved")
} catch {
print("error saving file:", error)
}
}
if let image = getSavedImage(named: "image.jpg") { Button1.setImage(image, for: .normal)
}
And here is my code for when the view loads:
if let image = getSavedImage(named: "image.jpg") {
Button1.setImage(image, for: .normal)
}
As #rmaddy mentioned you already saved image thats why image not replaced. but if you want on tap of button image will be change you need to do some extra work, you need to check if image.jpg exist then add new image with diff name.To check image exist use this method, This method return bool value and path of the file if file exist.So on these two values you can remove your existing image or create new image with different name, according to your code behaviour.
static func isFileExit(filename: String) -> (fileExist: Bool? , path: URL?) {
if let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
let filepath = URL(fileURLWithPath: dir.absoluteString).appendingPathComponent(filename)
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filepath.path) {
print("FILEAVAILABLE")
return (true, filepath)
}
}
return (false,nil)
}
You're saving your images only if the image doesn't exist, so when you try to save the second image, its not being saved because the first image already exists.
//you should double check what you're doing here
if let data = UIImageJPEGRepresentation(pickedImage, 1.0),
!FileManager.default.fileExists(atPath: fileURL.path) {
do {
// writes the image data to disk
try data.write(to: fileURL)
print("file saved")
} catch {
print("error saving file:", error)
}
}

Save and Retrieve Image File From Realm

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()
}

How to save text file as variable in IOS

I am sending some images via socket and I would like to create text file with information about the images to send over the network as well. I right now I can send the images no problem by creating a variable for the image data like so
let imageData = UIImageJPEGRepresentation(someUIImage, 1.0)
How do create a variable with the data of the text file?
let textData = someTextFileAsData.....
Is this what you want?
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
// Get the URL to the file. Below is an example
let fileURL = documentsDirectory.appendingPathComponent("test").appendingPathExtension("txt") // Replace "test" with your fileName and "txt" with your fileExtension
var text = ""
do {
text = try String(contentsOf: fileURL)
} catch {
fatalError("error: \(error.localizedDescription)")
}
Expanding a bit on Anthonin C.'s answer:
Each file in iOS is identified by a URL (just like a webpage, but this is a URL referring to the iOS filesystem). There are a few places in the system where you can save files, most of which you can get the URLs for by reading
FileManager.default.urls(for: SearchPathDirectory, in: SearchPathDomainMask)
(FileManager class reference).
For instance, to save in the user's personal "documents" directory, you would do:
let fileName = "socketLog.txt"
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
//build full path to file
let path = dir.appendingPathComponent(fileName)
do {
try text.write(to: path, atomically: false, encoding: String.Encoding.utf8)
}
catch {/* error handling here */}
}
where text would be the string data you want to save.
You should use write(to:​ URL, atomically:​ Bool) or write(to​File:​ String, atomically:​ Bool) of NSData method to write your data in a file.
In your case it would be :
let imageData = UIImageJPEGRepresentation(someUIImage, 1.0)
imageData.writeToFile("imageData.txt", atomically:true)
You can then restore it like that :
var imageData = NSData(contentsOfFile: "imageData.txt")
And to get back the image from data :
let image : UIImage = UIImage(data: imageData)

UIImageJPEGRepresentation | generateJPEGRepresentation saves image as nil? Swift 3

I am currently designing a database management application with Realm, where I have managed to create and retrieve an object successfully. The problem I am having is with updating/editing - specifically updating the UIImage that the user has uploaded. With Realm, I save the path of the image and then retrieve it by loading that path (in Documents Directory).
When the user tries to save the changed image, for some odd reason the UIImageJPEGRepresentation saves the changed image as nil, thus removing the user's image. It's strange because the initial creation of a data object stores it just fine.
I have tried to check whether the image is being passed correctly with some debugging, and have found that it does so just fine and the right path is being saved on.
Here is my update method:
func updateImage() {
let documentsDirectoryURL = try! FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL = documentsDirectoryURL.appendingPathComponent("\(selectedPicPath!)")
if FileManager.default.fileExists(atPath: fileURL.path) {
do {
if profilePic.image != nil {
let image = profilePic.image!.generateJPEGRepresentation()
try! image.write(to: fileURL, options: .atomicWrite)
}
} catch {
print(error)
}
} else {
print("Image Not Added")
}
}
Can anyone see any problems?
let image = profilePic.image!.generateJPEGRepresentation()
Check this line, whether it is returning nil value or data? If nil, then use following code to test your image store, it's working. Also ensure your actual image has JPEG file format extension, that you are trying to generate.
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
// For PNG Image
if let image = UIImage(named: "example.png") {
if let data = UIImagePNGRepresentation() {
let filename = getDocumentsDirectory().appendingPathComponent("copy.png")
try? data.write(to: filename)
}
}
For JPG image
if let image = UIImage(named: "example.jpg") {
if let data = UIImageJPEGRepresentation(image, 1.0) {
let filename = getDocumentsDirectory().appendingPathComponent("copy.jpg")
try? data.write(to: filename)
}
}

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 ;-)

Resources