Best way to store and recall array of images - swift

I am looking for the best way to store and recall an array of images. Currently I am storing the image URLs in a .plist file and loading into a collectionView doing this in cellForItemAt:
let imageURLString = arrayOfURLsFromPlist[indexPath.row]
let imageURL = URL(string: imageURLString)
let imageData = try? Data(contentsOf: imageURL!)
let image = UIImage(data: imageData!, scale: UIScreen.main.scale)
cell.image.image = image
This works but takes a few seconds to load. It also crashes when trying to load bigger image files. I guess thats because it hasn't loaded the image Data before I am trying to create an image from it.
What is the best way to store either the paths to an array of images or the image data in a plist and load it quickly as needed? I need to be able to load an array of images from the one plist file.
Apple seems to be able to load our entire camera roll instantly. How do I get that kind of performance?
EDIT: The url for the image is only in documents directory.

Related

SwiftUI List memory issue, images not deallocating from RAM causing crash

I am loading images into a SwiftUI List. When too many images are scrolled down, RAM skyrockets and crashes app. Why are images not deallocated as user scrolls down past them?
I am loading images as so:
List(allProducts, id: \.self) { product in
Image(uiImage: UIImage(data: dataFromRealmDB[product]))
}
My intuition tells me that there must be something to deallocate it manually from memory, so I am trying the following. Please let me know if you how to fill in the blank.
List(allProducts, id: \.self) { product in
Image(uiImage: UIImage(data: dataFromRealmDB[product])).onDisappear(perform: {
"WHAT SHOULD GO HERE TO MAKE THE IMAGE GET PURGED FROM RAM?"
}
}
If my suggested solution is not possible, please let me know as well.
UPDATE
I have changed the way images are stored. Now they are stored with FileManager instead of saving them to the RealmDB. This is my function to get the image. Still memory usage increase causing a crash, there is no deallocation from SwiftUI.
func getImage(link: String) -> Image? {
let lastPath = URL(string: link)?.lastPathComponent
if let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
let image : UIImage? = UIImage(contentsOfFile: URL(fileURLWithPath: dir.absoluteString).appendingPathComponent(lastPath!).path)
if image != nil {
return Image(uiImage: UIImage(contentsOfFile: URL(fileURLWithPath: dir.absoluteString).appendingPathComponent(lastPath!).path)!)
}
}
return Image("noImg")
}
The code in the question is a bit incomplete but the answer really isn't a coding answer, it's more of a overall design answer.
Realm objects are lazily loaded and will essentially never overrun memory if used properly; they are only in memory when in use; e.g. a Realm Results object having 10,000 objects is easily handled, memory is allocated and deallocated automatically.
On that note, if you store Realm objects in other, non-Realm objects like an array, that totally changes the memory impact and can overwhelm the device.
But, and more importantly:
Realm is not a good way to store Blob data (full size pictures). A Realm property has a finite amount of storage of 16Mb and an image can easily go way beyond that.
There are other options for picture storage from MongoDB and Firebase.
See my answer to this question for more details
Lastly, you should be using pagination to control how many images are loaded at a time from whatever source you use; that will allow you to more easily control the memory allocation and UI.
So that last part is important, no matter what the technique is, loading a bunch of images into memory is going to eventually overwhelm the device so as you can see, switching to using FileManger to load the images from disk instead of Realm is not going to be a long term solution; pagination is the way to go.
There are some third party libraries available to help with that and/or you can craft your own. Do a search here on SO for 'image pagination' as it's discussed a lot
Oh - one other thing; please use thumbnails for your UI! You only really need to display full size images if the user taps or selects it - then you can load it from disk. Firebase Storage can actually do that for you; when you upload an full size image, it can (on their server) create a thumbnail for your UI.
Likewise, thumbnails are tiny and Realm can easily handle those; the design would be an object like this
class MyImageObject: Object {
#Persisted var imageURL: //a string or url to where it's stored on disk
#Persisted var thumbnail: Data!
#Persisted var image_name = "" //the image name
}
Are you sure it is not related to the fact you fetch the image raw data from the Database? According this question a SwiftUI List works like a tableview - i.e reusing cells.
I think the fact you are using a data base to hold the raw data of the images causes the spike in memory.
This is kind of an opinion based answer but Iā€™d recommend on either bundling the images in advance in the application (if your business logic supports it), using names in the DB and init them by name.
Or hosting them remotely and fetching on demand.

Performance problem with saving images locally and writing to api in Swift 4

We are transforming images to a byte array and then saving them as a string in a JSON file. This happens locally on the file system. When reading this JSON file from the file system or writing it to the API the performance is really bad. For large quantities of images you have to wait >10 sec, before it is loaded in the ViewController. Is there a better way or library (Swift4) to deal with this? We are already compressing the quality to 0.2. Someone told us to use base64, is this the way to go?
We encode the image like this:ImagesBytes : [[UInt8]]? in to a JSON File with some TextField data.
This is the code
public var formImagesBytes : [[UInt8]]? = [] So this is in the model as a field. The model is a form with fields(strings) and an array of images which is an array of UInt8. This will be first saved locally while decoding it like this:
formImagesBytes = try container.decodeIfPresent([[UInt8]].self, forKey: .formImagesBytes)
Then we JSON encode all the data, including values of the form and image array of bytes. Then we add it with the Filemanager to the local storage on the device. We can open this file and it is including the formdata and an array of bytes in this case.
When loading the data from the local storage in the viewcontroller we again read this local file en decode it to a form model. But processing this file is taking too much time. I hope it is clear. My question is not really about the code, but about: is there a best practice or better way to write the image to this JSON file in order to let the app perform better.
Example output:
{"Images":[[255,216,255,224,0,16,74,70,73,70,0,1,1,0,0,144,0,144,0,0,255,225,0,140,69,120,105,102,0,0,77,77,0,42,0,0,0,8,0,5,1,18,0,3,0,0,0,1,0,1,0,0,1,26,0,5,0,0,0,1,0,0,0,74,1,27,0,5,0,0,0,1,0,0,0,82,1,40,0,3,0,0,0,1,0,2,0,0,135,105,0,4,0,0,0,1,0,0,0,90,0,0,0,0,0,0,0,144,0,0,0,1,0,0,0,144,0,0,0,1,0,3,160,1,0,3,0,0,0,1,0,1,0,0,160,2,0,4,0,0,0,1,0,0,1,82,160,3,0,4,0,0,0,1,0,0,0,253,0,0,0,0,255,237,0,56,80,104,111,116,111,115,104,111,112,32,51,46,48,0,56,66,73,77,4,4,0,0,0,0,0,0,56,66,73,77,4,37,0,0,0,0,0,16,212,29,140,217,143,0,178,4,233,128,9,152,236,248,66,126,255,192,0,17,8,0,253,1,82,3,1,34,0,2,17,1,3,17,1,255,196,0,31,0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,255,196,0,181,16,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125,1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250,255,196,0,31,1,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,255,196,0,181,17,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119,0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250,255,219,0,67,0,28,28,28,28,28,28,48,28,28,48,68,48,48,48,68,92,68,68,68,68,92,116,92,92,92,92,92,116,140,116,116,116,116,116,116,140,140,140,140,140,140,140,140,168,168,168,168,168,168,196,196,196,196,196,220,220,220,220,220,220,220,220,220,220,255,219,0,67,1,34,36,36,56,52,56,96,52,52,96,230,156,128,156,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,255,221,0,4,0,22,255,218,0,12,3,1,0,2,17,3,17,0,63,0,223,162,138,40,16,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,1,255,208,223,162,138,40,16,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,1,255,209,223,162,138,40,16,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,1,255,210,223,162,138,40,16,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0,81,69,20,0]],
Here is a screenshot:
Output of the Image in a ByteArray
The JSON you posted explains why it is taking so long to parse. It probably takes a while to parse these huge arrays.
Instead of having each image as an array of bytes it should just be one base64 encoded string for each image. Something like this:
public var formImagesBase64 : [String]?
Here is some Swift code that might help you:
// Encoding Image
let data = image.jpegData(compressionQuality: 1)
let b64string = data?.base64EncodedString()
// Decoding image
let decodedData = Data(base64Encoded: b64string!)
let decodedImage = UIImage(data: decodedData!)

swift: image is nil after converting to CIImage

I am new to swift and I am trying the CoreML feature using React Native.
I have a source file with URL as string. I convert that string to URL using let imageURL = URL(fileURLWithPath: source) and I tried printing out the source which is of type string and it contains the absolutePath to the file file:///var/mobile/Containers/Data/Application/9C4D58FD-C817-47CB-A418-04BA30863C24/Library/Caches/Camera/133F8168-356A-436C-B7AF-557FA1C4F68F.jpg"
After that, I try to make the CIImage by using let image = CIImage.init(contentsOf: imageURL);
But when I try to print image, it returns nil. I searched a bit and tried using guard but no luck. Any help will be appreciated. Thanks in advance.
The CIImage does not create the image from a server URL. You can achieve this by first downloading the file at local location. And use the local file URL to create CIImage and then set it. The referred question/answer achieves the same, please check Loading/Downloading image from URL on Swift
If you still face issues do let me know. All the best!

Save and retrieve image in Swift 3.0

I am really confused of all the different possibilities Swift has to save data and retrieve data. Save and retrieve data through userDefaults is easy, but for storing a lot of images this is not the best way to do it. Then there is Core Data, which is I think to complicated to use for such a simple task. I continued to read about DocumentDirectory, I think this is a good way to save and retrieve data. However, all the tutorials are mostly from swift 2.0 and below, and I can not read that very well. Is the document directory the best way to store and retrieve images? I am making an online application which contains downloading images from users. However, the images are all the same from the same user, so I want to store the images offline to save some bandwidth.
I want to store and retrieve images with a path I can specify in a String. Every user has a unique identifier which I want to use as a path. How can I accomplish this?
Saving in your file system is definitely the best approach to use in this scenario.
Have a look to this answer:
https://stackoverflow.com/a/39896556/5109911
if let data = UIImageJPEGRepresentation(image, 0.8) {
let filename = getDocumentsDirectory().appendingPathComponent("copy.png")
try? data.write(to: filename)
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
where image is your UIImage
To Store Image in User default you can just simply create an extension of UI image view like šŸ‘‡šŸ» below Code Click the link to show code
https://i.stack.imgur.com/xEhcr.png
How to Use::- yourimageviewName.saveImage(),OtherImageViewName.loadImage()
https://i.stack.imgur.com/wcrNX.png

App Hangs when saving in core data

I'm using swift core data to save user's info within the app but when its saving current users data (name,bio, 3-4 photos) it hangs for 2-3 seconds until it saves the data. What is the best way to avoid this hanging of the app? this is the code I'm using
for photo in photos {
let data = NSData(contentsOfURL: NSURL(string: photo.url!)!)
let newimg = UIImage(data: data!)
newusersImages.addObject(newimg!)
}
mayBeUser.name = currentuser!.objectForKey("name") as! String
let arrayData = NSKeyedArchiver.archivedDataWithRootObject(newusersImages)
let locationData = NSKeyedArchiver.archivedDataWithRootObject(currentuser!.objectForKey("location")!)
mayBeUser.photos = arrayData
mayBeUser.location = locationData
mayBeUser.about = currentuser!.objectForKey("about") as! String
mayBeUser.setValue(currentuser!.objectForKey("age") as! Int, forKey: "age")
mayBeUser.objectId = currentuser!.objectId!
mayBeUser.lastSeen = currentuser!.objectForKey("lastSeen") as! NSDate
try! context.save()
print("Current User Updated")
when the user has 10-15 friends , it takes hangs for a minute to save/update all the info/data of the users.
Here's how I'd do it. Now obviously you don't want the UI to hang, so you'll want to use a managed object context that has a private queue concurrency type. More info on that here. Specifically, look at the part at the end where he details how to create the export function.
Now, since you're saving data to the persistent store in this case, you might not want the user to go around changing stuff while their previous changes are being saved, in this case you might want an interim screen showing the progress of the save.
You (and your app) are having to work hard to save all that photo data into one attribute of your User entity. Instead, restructure your data model:
Create a separate Photo entity for the photos.
Don't store the photo image data in CoreData - just store a string with the URL.
Add a to-many relationship from User to Photo, instead of the photos attribute.
Incidentally, I would avoid using objectId as an attribute name: it's so close to CoreData's objectID it will cause confusion.