Storing photos locally persistently - ios

I want to store a couple of images locally in my app on the user's device.
What I was using until now (it's still in development):
static func filePath(forKey key: String) -> URL? {
let fileManager = FileManager.default
guard let documentURL = fileManager.urls(for: .documentDirectory,
in: FileManager.SearchPathDomainMask.userDomainMask).first else { return nil }
return documentURL.appendingPathComponent(key + ".png")
}
static func savePhoto(imageKey: String) {
if let filePath = Helpers.filePath(forKey: imageKey) {
do {
try Constants.PHOTO_DATA.write(to: filePath, options: .atomic)
} catch {
print("error")
}
} else {
print(" >>> Error during saving photo. Filepath couldn't be created.")
}
}
static func getPhoto(imageKey: String) -> (image: UIImage, placeholder: Bool) {
if let filePath = Helpers.filePath(forKey: imageKey),
let fileData = FileManager.default.contents(atPath: filePath.path),
let image = UIImage(data: fileData) {
// Retrieve image from device
return (image, false)
}
return (UIImage(named: "placeholder")!, true)
}
Now, during testing I realized that it is not working (but I'm almost 100% sure it was working until now, strange..). It is changing the App's container directory upon every launch.
E.g.
Path:
/var/mobile/Containers/Data/Application/1F3E812E-B128-481C-9724-5E39049D6C81/Documents/D5F14199-CFBF-402A-9894-3487976C4C74.png
Restarting the app, then the path it gives (and where it does not find the image):
/var/mobile/Containers/Data/Application/0A9FCE45-1ED4-46EB-A91B-3ECD56E6A31B/Documents/D5F14199-CFBF-402A-9894-3487976C4C74.png
I read a bit and as far as I see it is 'expected' that it is not working, as the app's directory can change any time the user restarts the app. I should use bookmarkData of the URL class.
My problem is that I couldn't get it working with bookmarkData as I don't really see how should I use it, and couldn't understand its behavior based on some example codes/articles I found. Until now I was simply using URLs to store/retrieve the photo but now I should go with this bookmarkData which is a Data type, which confuses me.

I'm not sure what you want your code means, since both Helper and Constants.PHOTO_DATA are unknown. The code that will definitely will save a UIImage in the documents directory is here:
class ImageSaver {
private let imageStore = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first
//Make this static variable to allow access from all objects without instantiating this class
static var shared : AuxiliaryObjects {
return AuxiliaryObjects()
}
/**
Declaration: save(image : UIImage, with fileName: String, and imageName: String?)
Description: This method saves the received image to the persistent store in the documents directory of the user.
- Parameter image: The UIImage object that must be stored in the documents directory.
- Parameter fileName: A string with the name under which the image must be stored.
- Parameter imageName: The name of the image if needed.
*/
func save(image: UIImage, with fileName: String, and imageName: String?) {
let fileStore = imageStore?.appendingPathComponent(fileName)
let imageData = UIImagePNGRepresentation(image)
do {
try imageData?.write(to: fileStore!)
} catch {
print("Couldn't write the image to disk.")
}
}
/**
Declaration: getImage(with fileName: String, with rectangle: CGRect) -> UIImage?
Description: This method retrieves the image with the specified file name and a given size.
- Parameter fileName: a string with the file name to retrieve.
- Parameter rectangle: the size of the image to return.
- Returns: UIImage?, the image retrieved from the documents directory.
*/
func getImage(with fileName: String, with rectangle: CGRect) -> UIImage? {
var returnImage : UIImage?
var imageRectangle = rectangle
do {
imageStoreArray = try FileManager.default.contentsOfDirectory(at: imageStore!, includingPropertiesForKeys: resourceKeys, options: .skipsHiddenFiles) as [NSURL]
} catch {
return returnImage
}
for url in imageStoreArray {
let urlString = url.lastPathComponent
if urlString == fileName {
let retrievedImage = UIImage(contentsOfFile: url.path!)
//When there is no size set, the original size image is returned
if (rectangle.size.height > 0) || (rectangle.size.width > 0) {
let imageWidth = retrievedImage?.size.width
let imageHeight = retrievedImage?.size.height
if imageWidth! > imageHeight!
{
//The picture is wider than it is high
imageRectangle.size.height *= (imageHeight! / imageWidth!)
} else {
imageRectangle.size.width *= (imageWidth! / imageHeight!)
}
UIGraphicsBeginImageContextWithOptions(imageRectangle.size, false, UIScreen.main.scale)
retrievedImage?.draw(in: imageRectangle)
returnImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
} else {
returnImage = retrievedImage
}
}
}
return returnImage
}
}
Let me know if this works for you.
Kind regards,
MacUserT

Related

UIImage is rotated 90 degrees when creating from url and set to the pasteboard

What do I simply do?
let pasteboard = UIPasteboard.general
let base64EncodedImageString = "here_base_64_string_image"
let data = Data(base64Encoded: base64EncodedImageString)
let url = data?.write(withName: "image.jpeg")
pasteboard.image = UIImage(url: url) //and now when I try to paste somewhere that image for example in imessage, it is rotated... why?
What may be important:
It happens only for images created by camera.
However, if use exactly the same process (!) to create activityItems for UIActivityViewController and try to use iMessage app, then it works... why? What makes the difference?
I use above two simple extensions for UIImage and Data:
extension Data {
func write(withName name: String) -> URL {
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(name)
do {
try write(to: url, options: NSData.WritingOptions.atomic)
return url
} catch {
return url
}
}
}
extension UIImage {
convenience init?(url: URL?) {
guard let url = url else {
return nil
}
do {
self.init(data: try Data(contentsOf: url))
} catch {
return nil
}
}
}
Before server returns base64EncodedString I upload an image from camera like this:
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
let image = info[.originalImage] as? UIImage
let encodedBase64 = image?.jpegData(compressionQuality: 0.9)?.base64EncodedString() ?? ""
//upload encodedBase64 to the server... that is all
}
I am not sure but I think UIPasteBoard converts your image to PNG and discards its orientation. You can explicitly tell the kind of data you are adding to the pasteboard but I am not sure if this would work for your scenery.
extension Data {
var image: UIImage? { UIImage(data: self) }
}
setting your pasteboard data
UIPasteboard.general.setData(jpegData, forPasteboardType: "public.jpeg")
loading the data from pasteboard
if let pbImage = UIPasteboard.general.data(forPasteboardType: "public.jpeg")?.image {
}
Or Redrawing your image before setting your pasteboard image property
extension UIImage {
func flattened(isOpaque: Bool = true) -> UIImage? {
if imageOrientation == .up { return self }
UIGraphicsBeginImageContextWithOptions(size, isOpaque, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: size))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
UIPasteboard.general.image = image.flattened()

How to save Image with its custom name in IOS?

I need to capture image from image picker controller and save image with its custom name and later on fetch all the images with their respective names .
try this
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let filePath = "\(paths[0])/MyImageName.png"
// Save image.
UIImagePNGRepresentation(image)?.writeToFile(filePath, atomically: true)```
I assume you already have the picker working in order to get an image from the gallery, and only wants to save and get it from the app folder.
I was able to make this with the following code:
Class ImagePersistance.swift
open class ImagePersistance: NSObject {
var fileManager: FileManager
var documentsURL: URL
var documentPath: String
public override init() {
self.fileManager = FileManager.default
self.documentsURL = self.fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
self.documentPath = documentsURL.path
}
public func saveImage(image: UIImage?, name: String) throws {
if image != nil {
let filePath = documentsURL.appendingPathComponent("\(String(name)).png")
if let currentImage = image {
if let pngImageData = UIImagePNGRepresentation(currentImage) {
try pngImageData.write(to: filePath, options: .atomic)
}
}
}
}
public func getImage(name: String) -> UIImage? {
let filePath = documentsURL.appendingPathComponent("\(String(name)).png")
if FileManager.default.fileExists(atPath: filePath.path) {
if let image = UIImage(contentsOfFile: filePath.path) {
return image
}
}
return nil
}
}
How to use it:
Save image:
do {
try ImagePersistance().saveImage(image: IMAGE_HERE, name: "IMAGE_NAME")
catch {
print("image error")
}
Get saved image
let image:UIImage = ImagePersistance().getImage(name: "IMAGE_NAME")

Loading images from a URL and storing them locally using Swift4

I am needing to load images from a URL and store them locally so they dont have to be reloaded over and over. I have this extension I am working on:
extension UIImage {
func load(image imageName: String) -> UIImage {
// declare image location
let imagePath: String = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(imageName).png"
let imageUrl: URL = URL(fileURLWithPath: imagePath)
// check if the image is stored already
if FileManager.default.fileExists(atPath: imagePath),
let imageData: Data = try? Data(contentsOf: imageUrl),
let image: UIImage = UIImage(data: imageData, scale: UIScreen.main.scale) {
return image
}
// image has not been created yet: create it, store it, return it
do {
let url = URL(string: eventInfo!.bannerImage)!
let data = try Data(contentsOf: url)
let loadedImage: UIImage = UIImage(data: data)!
}
catch{
print(error)
}
let newImage: UIImage =
try? UIImagePNGRepresentation(loadedImage)?.write(to: imageUrl)
return newImage
}
}
I am running into a problem where the "loadedImage" in the UIImagePNGRepresentation comes back with an error "Use of unresolved identifier loadedImage". My goal is to store a PNG representation of the image locally. Any suggestions on this error would be appreciated.
It's a simple matter of variable scope. You declare loadedImage inside the do block but then you attempt to use outside (after) that block.
Move the use of loadedImage to be inside the do block.
You also need better error handling and better handling of optional results. And your load method should probably return an optional image incase all attempts to get the image fail. Or return some default image.
Here's your method rewritten using better APIs and better handling of optionals and errors.
extension UIImage {
func load(image imageName: String) -> UIImage? {
// declare image location
guard let imageUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent(imageName).appendingPathExtension("png") else {
return nil // or create and return some default image
}
// check if the image is stored already
if FileManager.default.fileExists(atPath: imageUrl.path) {
if let imageData = try? Data(contentsOf: imageUrl), let image = UIImage(data: imageData) {
return image
}
}
// image has not been created yet: create it, store it, return it
do {
let url = URL(string: eventInfo!.bannerImage)! // two force-unwraps - consider better handling of this
if let data = try Data(contentsOf: url), let loadedImage = UIImage(data: data) {
try data.write(to: imageUrl)
return loadedImage
}
}
catch{
print(error)
}
return nil // or create and return some default image
}
}
If eventInfo!.bannerImage is a remote URL, then you must never run this code on the main queue.

ios FileManager losing stored data when rebuilding app

I'm trying to store images inside the app's document folder, so the user can retrieve them at any later time that they want to. This is my code to store them:
func store(_ image: UIImage) -> String {
let imageName = "\(Date().timeIntervalSince1970)"
let imagePath = "\(documentasPath)/\(imageName).png"
let imageData = UIImagePNGRepresentation(image)
fileManager.createFile(atPath: imagePath, contents: imageData, attributes: nil)
return imagePath
}
And this is my code to retrieve the image from the storage:
func retrieveImage(from path: String) -> UIImage? {
guard fileManager.fileExists(atPath: path) else {
return nil
}
return UIImage(contentsOfFile: path)
}
It seems to work fine, except when I rebuild the app from xcode. Then all of my stored images disappear (although all of the paths I stored that pointed to them are still present and correct).
Is this some behavior of the default file manager? And is there a way to avoid this from happening? I want the images to only be deleted either manually or when I uninstall the app.
Thanks
The problem is that you are storing an absolute path. You can't do that, because your app is sandboxed, which means (in part) that the URL of the Documents folder can change. Store just the document name, and each time you want to save to it or write from it, calculate the path to the Documents folder again and append the document name and use that result as your path.
Change to this
func store(_ image: UIImage) -> String {
let imageName = "\(Date().timeIntervalSince1970)"
let documentsUrl = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
var imagePath = documentsUrl.appendingPathComponent("\(imageName).png")
let imageData = UIImagePNGRepresentation(image)
fileManager.createFile(atPath: imagePath, contents: imageData, attributes: nil)
return imagePath
}
func retrieveImage(from path: String) -> UIImage? {
guard fileManager.fileExists(atPath: path) else {
return nil
}
return UIImage(contentsOfFile: path)
}

How can I save a photo taken with UIImagePickerController camera to be displayed later in the app?

I am having the user take a photo with UIImagePickerController and I need it to be saved to the app so that it can be displayed when they need to see it later. How can I accomplish this?
I have heard NSUserDefaults would be a mistake. All I need to save is a single image, never more.
Here is the function to save your image to document folder
func saveImage (image: UIImage, path: String ) -> Bool{
let pngImageData = UIImagePNGRepresentation(image)
//let jpgImageData = UIImageJPEGRepresentation(image, 1.0) // if you want to save as JPEG
let result = pngImageData.writeToFile(path, atomically: true)
return result
}
Here is the function to get your document directory path with image name
func fileInDocumentsDirectory(filename: String) -> String {
let documentsFolderPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0] as! String
return documentsFolderPath.stringByAppendingPathComponent(filename)
}
Here is an example of how to use your image path
let imagePath = fileInDocumentsDirectory(myImageName)
And here is how you save your image to that folder
saveImage(image, path: imagePath)
Now the last part is to get your image, so use this function to get your image
func loadImageFromPath(path: String) -> UIImage? {
let image = UIImage(contentsOfFile: path)
if image == nil {
println("Image not available at: (path)")
}
return image
}
And here is how you use the above function
image = loadImageFromPath(imagePath)
imageView.image = image
Hope this helps you

Resources