I want to save images in an array using UserDefaults in Swift 3
var arrayImage = [UIImage]()
arrayImage.append(UIImage(data: dataImage as! Data)!) //add image to array
globalD.set(arrayImage, forKey: "FavoritesContactStandardImage")
I'm doing it that way, but it does not save, how can I save it and then read it?
Maybe you could try these. You just use saveImages & loadImages functions.
But you need to implement delete function by yourself.
let key = "FavoritesContactStandardImage"
func getImageKey(_ index:Int) -> String {
return "\(key)\(index)"
}
func saveImages(_ images:[UIImage]) {
var list = UserDefaults.standard.array(forKey: key) as? [String] ?? [String]()
var index = list.count
for image in images {
let imgKey = getImageKey(index)
saveImage(imgKey, image)
list.append(imgKey)
UserDefaults.standard.set(list, forKey: key)
UserDefaults.standard.synchronize()
index += 1
}
}
func saveImage(_ imageName:String, _ image:UIImage) {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let imgPath = URL(fileURLWithPath: path.appendingPathComponent(imageName))
do {
try UIImagePNGRepresentation(image)?.write(to: imgPath, options: .atomic)
} catch let error {
print(error.localizedDescription)
}
}
func loadImages() -> [UIImage] {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let list = UserDefaults.standard.array(forKey: key) as? [String] ?? [String]()
var imageList = [UIImage]()
for (index, _) in list.enumerated() {
let imageName = getImageKey(index)
let imgPath = URL(fileURLWithPath: path.appendingPathComponent(imageName))
if let image = UIImage(contentsOfFile: imgPath.path) {
imageList.append(image)
}
}
return imageList
}
Hope that's helpful.
That's a really bad idea to save image data on UserDefault , maybe better if you save the image name (if you put your image on assets catalog) / image path (if your put your image on custom directory on your project ). And if you want to save the downloaded image from services maybe you can save your downloaded image to a directory before you get the paath and save to UserDefault 😉
Related
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")
Every time I show the profile picture, the UIImageView flashes to signify that the image was just downloaded from the Firebase Storage URL. This download speed differs based on the device type, some times it is unnoticeable while other times there is a significant delay.
I have attempted to cache the image with NSCache and the Kingfisher library but I still see the UIImageView flash rather than remain there every time I reopen the app.
My last attempt was to save the image to the document directory and then retrieve it from there but I still see the image flash. I would also like the profile picture to remain there even if the application is opened without any internet connection.
func saveImageDocumentDirectory(imgUrl: URL){
let fileManager = FileManager.default
let paths = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("proPic.png")
let data = (try? Data(contentsOf: imgUrl))
let image = UIImage(data: data!)
print("\n\(paths)\n")
let imageData = image!.pngData()
fileManager.createFile(atPath: paths as String, contents: imageData, attributes: nil)
}
func getDirectoryPath() -> String {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
return documentsDirectory
}
func getImage(){
let fileManager = FileManager.default
let imagePAth = (self.getDirectoryPath() as NSString).appendingPathComponent("proPic.png")
if fileManager.fileExists(atPath: imagePAth){
self.profilePic.image = UIImage(contentsOfFile: imagePAth)
}else{
print("\nNo Image\n")
}
}
func createDirectory(){
let fileManager = FileManager.default
let paths = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("customDirectory")
if !fileManager.fileExists(atPath: paths){
try! fileManager.createDirectory(atPath: paths, withIntermediateDirectories: true, attributes: nil)
}else{
print("\nAlready dictionary created.\n")
}
}
And I would call the function by:
func getEmailPic(){
guard let uid = Auth.auth().currentUser?.uid else {return}
//receive the location of the profile pic
let storageRef = Storage.storage().reference().child(uid).child("profilePic.png");
//how to access the downloadURL
_ = storageRef.downloadURL(completion: { (URLe, error) in
if let error = error{
//error handling
print("\nCould not download user's profile image from url.
Error: \(error.localizedDescription)\n");
return;
}
self.createDirectory()
self.saveImageDocumentDirectory(imgUrl: URLe!)
print("\nThis is the URL: \(URLe)\n")
self.getImage()
})
}
in viewDidLoad.
Using kingfisher for image caching, Try this and feel free to ask if facing any issue
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// set profile image if you have url saved in userdefaults
let imageUrl = getUrlImageFromUserDefaults()
let placeholderImage = UIImage(named: "placeholder")
profileImageView.kf.setImage(with: imageUrl, placeholder: placeholderImage)
getEmailPic()
}
func getUrlImageFromUserDefaults() -> URL?{
// save image URL to userdefault and fetch here
let userdefaults = UserDefaults.standard
return userdefaults.url(forKey: "profileURL")
}
func getEmailPic(){
guard let uid = Auth.auth().currentUser?.uid else {return}
//receive the location of the profile pic
let storageRef = Storage.storage().reference().child(uid).child("profilePic.png");
//how to access the downloadURL
_ = storageRef.downloadURL(completion: { (URLe, error) in
if let error = error{
//error handling
print("\nCould not download user's profile image from url.
Error: \(error.localizedDescription)\n");
return;
}
if URLe == getUrlImageFromUserDefaults() {
// if url is same no need to set again
}else{
// set profile image
let placeholderImage = UIImage(named: "placeholder")
profileImageView.kf.setImage(with: URLe, placeholder: placeholderImage)
// and again save this new URL to userdefaults
}
})
}
I have folder with many files in documents directory. In my folder I have files with different extensions (jpg, png, mp3, zip). In my code I have stringArray. I want to load all files in someArray and after that add files with .png extension from someArray in stringArray How to do it?
Is it possible to do this? Or I should find another way to load multiple files from documents directory?
I find answer for Load multiple images from the folder or directory. - Swift 4
I tried to use this code from answer:
func loadImagesFromAlbum(folderName:String) -> [String]{
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
var theItems = [String]()
if let dirPath = paths.first
{
let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(folderName)
do {
theItems = try FileManager.default.contentsOfDirectory(atPath: imageURL.path)
return theItems
} catch let error as NSError {
print(error.localizedDescription)
return theItems
}
}
return theItems
}
But I can't get files from theItems. Because theItems it a string array. What I do wrong? How to get files from loadImagesFromAlbum?
I tried to use this code:
images = loadImagesFromAlbum(folderName: "/folder1")
But it is not help me. I get only names. But I need to get files.
Update
I use this code to load image from documents directory:
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
if let dirPath = paths.first
{
let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent("/folder1/1.png")
myImageView.image = UIImage(contentsOfFile: imageURL.path)
}
But I need to load 100 images from /folder1 in var images = [String](). Because I use var images = [String]() to show images: contentVC.imageName = images[index] // and etc...
And the code I use above is not very convenient if I need add 40-100 images.
Update 1
I have this code to load images from my project and show it:
class PageViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
for page in 1...pageNumber[indexPage] {
images.append("page\(page).png")
}
}
func getContentViewController(withIndex index: Int) -> ContentViewController? {
if index < images.count{
let contentVC = self.storyboard?.instantiateViewController(withIdentifier: "ContentViewController") as! ContentViewController
contentVC.itemIndex = index
contentVC.imageName = images[index]
return contentVC
}
return nil
}
}
import UIKit
class ContentViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var itemIndex: Int = 0
var imageName: String?
override func viewDidLoad() {
super.viewDidLoad()
if let currentImage = imageName{
imageView.image = UIImage(named: currentImage)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
But I need to load images from documents directory and show it. How to do it?
If I understand your question correctly you want to load an image for a specific name in a specific folder in the Documents folder.
Please try this, the method takes two parameters, the file name (with extension!) and the folder name and returns an UIImage or nil if the image cannot be found or created.
func loadImage(withName name : String, from folderName: String) -> UIImage? {
do {
let documentsFolderURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let imageURL = documentsFolderURL.appendingPathComponent(folderName).appendingPathComponent(name)
let data = try Data(contentsOf: imageURL)
return UIImage(data: data)
} catch {
return nil
}
}
There is no benefit to get all file paths with contentsOfDirectory because you have to load the image data one by one anyway.
I've a CKRecord type created in the CloudKit backend with some properties related to that class.
I've String properties, Bytes and I have a Asset List property, so store some images (multiple images related to a single record).
Now I'm trying so store some images and then fill the property and then trying to save it to CloudKit, but it's not working.
Code goes as it follows:
var images_array = [CKAsset]()
// append the an image to the array
images_array.append(CKAsset(fileURL: writeImage(image: selectedImage) as URL))
let record = CKRecord(recordType: recordName)
record["class_title"] = someString as CKRecordValue
record["class_body"] = someString as CKRecordValue
record["images_array"] = images_array as CKRecordValue
saveRecord(record)
func saveRecord(_ xrecord: CKRecord) {
let publicData = CKContainer.default().publicCloudDatabase
let record: [CKRecord] = [xrecord]
let saveOperation = CKModifyRecordsOperation.init(recordsToSave: record, recordIDsToDelete: nil)
saveOperation.perRecordCompletionBlock = {(record, error) -> Void in
if (error != nil) {
print("error")
}
}
publicData.add(saveOperation)
}
func writeImage(image: UIImage) -> URL {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = NSURL(fileURLWithPath: documentsURL.absoluteString).appendingPathComponent(".jpg")
if let imageData = image.lowestQualityJPEGNSData {
do {
try imageData.write(to: fileURL!)
} catch {
print("ERRO 001 = \(error.localizedDescription)")
}
}
return fileURL!
}
extension UIImage {
var uncompressedPNGData: Data? { return UIImagePNGRepresentation(self) }
var highestQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 1.0) }
var highQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 0.75) }
var mediumQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 0.5) }
var lowQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 0.25) }
var lowestQualityJPEGNSData:Data? { return UIImageJPEGRepresentation(self, 0.0) }
}
If I only save the strings, everything works perfectly but with images it doesn't save the record.
I know there might be any issue with the appending, or I have to save the array in other way, or I shouldn't save it as CKRecordValue.
Do you have any tip on how to achieve this?
Thanks
When you create your local asset file you should do so with the atomic write option. This will ensure that the file is completely written before CloudKit attempts to upload the asset.
This is the asset file creation function I use in the Seam 3 library:
fileprivate func createAsset(data: Data) -> CKAsset? {
var returnAsset: CKAsset? = nil
let tempStr = ProcessInfo.processInfo.globallyUniqueString
let filename = "\(tempStr)_file.bin"
let baseURL = URL(fileURLWithPath: NSTemporaryDirectory())
let fileURL = baseURL.appendingPathComponent(filename, isDirectory: false)
do {
try data.write(to: fileURL, options: [.atomicWrite])
returnAsset = CKAsset(fileURL: fileURL)
} catch {
print("Error creating asset: \(error)")
}
return returnAsset
}
You have to take Array of CKAsset for images.
var imageUrls = [CKAsset]()
Now get all images using for-loop. And save CKAsset of images.
for images in self.arrayImageSelected{
var myImage = UIImage()
if (images.isKindOfClass(PHAsset)){
let imageC = images as? PHAsset
myImage = self.getAssetThumbnail(imageC!)
}else if (images.isKindOfClass(UIImage)){
myImage = (images as? UIImage)!
}
let imagePath = self.storeImageAtDocumentDirectory(myImage, titleName: self.strTitle)
myPAth.append(imagePath)
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
let FbPath = paths.stringByAppendingString("/Custom")
let filePathToWrite = "\(FbPath)" + imagePath
let urls = NSURL(fileURLWithPath: filePathToWrite)
let imageAsset = CKAsset(fileURL: urls)
imageUrls.append(imageAsset)
}
Set Your array.
record.setObject(imageUrls, forKey: "images_array")
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