Save and retrieve image in Swift 3.0 - swift

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

Related

Best way to store and recall array of images

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.

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

Accessing File saved in today extension from project

I am trying to access a file I saved from my today extension.In my today extenson I did this to save the file:
func startRecording() {
let audioFilename = getDocumentsDirectory().appendingPathComponent("recording.m4a")
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
audioRecorder.delegate = self
audioRecorder.record()
recordButton.setTitle("Stop", for: .normal)
} catch {
finishRecording(success: false)
}
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
I then tried to get the data for my AVAudio Player in the main part of the project using this code:
let path = Bundle.main.path(forResource: "recording.m4a", ofType:nil)!
let url = URL(fileURLWithPath: path)
However, it gave the error: unexpectedly found nil while unwrapping an Optional value.
Thanks for the help.
Your extension saves the file to its document directory and your app code is looking for the file in the app bundle. The app bundle only contains the resources that are distributed with the app. You'll need to delve into the file system.
However, there's another problem. The extension and containing app don't share a documents directory. They each have their own container for writing data local to themselves. If you want to share data between them, it's a little more work. In summary:
Create an app group identifier for the app and the extension to share.
Use FileManager.containerURL(forSecurityApplicationGroupIdentifier:) to get the file URL for the shared container directory.
From the container URL, append the file name.
In the extension, you'll set up the AVAudioRecorder as usual and start recording.
In the main app, you'll want to use the NSFileCoordinator API to ensure that only one process is writing to the file at a time. Hopefully, the AVAudioRecorder uses the NSFileCoordinator API internally, although I didn't immediately find confirmation of this.
For more details about shared containers, see this blog post.
I just tried the same - record audio from a Today Extension. The code looks sooo familiar, so I'm taking a wild guess: you want to capture voice and send the file to the Google Speech API, correct?
Nonetheless, I think we're hitting restrictions of extensions: judging by https://developer.apple.com/library/content/qa/qa1872/_index.html extensions cannot record audio. The article has been writting for iOS 8, but I don't believe Apple ever lifted the restriction. Please correct me if I'm wrong, since I keep running into problems doing what OP does myself.
btw, check the result of audioRecorder.record(). It might be false and indicate that the capture never started (that's my error currently).

How to show pdf data from a filestream

I am working against a REST API where you can make a call and then receive a image or pdf. I am using URLSession.shared.dataTask to make the call and when there is a image the call is a success (but it takes quite a long time, more then 5 seconds) and I can show the image in a UIImageView. But when there is a pdf, I don't know how to handle the result. The API returns the image / pdf as a ”filestream”.
When I print the data to the console it prints the size (bytes) and its the same size as in the server so in some way I have the correct data, I just don't know how to view the pdf.
I am using swift 3, iOS 10, xcode 8.
First of all you may want to ask your question into two part. Please edit it and ask the second part again.
There are two parts in this topic
1. Downloading the PDF and save it in File System
2. Get the pdf that saved in File System and read it using UIWebView or UIDocumentInteractionController
So, I will explain for the first one.
The first one can be done if you use REST HTTP client : Alamofire : Elegant HTTP Networking in Swift. So no need to use URLSession for such case and you will have to write so many lines if you do. It's simple and easy. So, I want you to try it. If you need URLSession instead, leave comment.
So how to download pdf using Alamofire :
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
//.documentDirectory means it will store in Application Document Folder
let fileURL = documentsURL.appendPathComponent("data.pdf")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(urlString, to: destination).response { response in
print(response)
if response.error == nil, let filePath = response.destinationURL?.path {
// do anything you want with filePath here.
// Here what you have to do Step 2. (Read the file that you downloaded)
}
}
This download procedure doesn't include requesting download link with encoded parameters. It was just simple way.

How to get ID3 tags for media files on iPhone

I need some help.
The situation looks like this:
I can get list of media files (mp3s, m4as, m4vs an so on) with link to the file (looks like:
http://10.0.1.1/Media/Files%20Folder/File%20Itself.m4v
So I get an array consisting of links to these files.
I need to display these items in UITableView with corresponding tags (Genre, Artist etc.) and most importantly, album art (it's embedded in file).
How can I fetch that information? If possible, without loading whole media file.
Thanks for any help.
I wrote a class for this
It's called metadataRetriever. It returns an array of the
artist,
song,
and album
as NSStrings (in that order)
It's really fast and is safe for synchronous use.
One caveat, it doesn't work for .m4a's because the id3 info for .m4a's is different.
You'll want to use AVURLAsset along with the method -loadValuesAsynchronouslyForKeys:completionHandler:.
Also note that the docs say
AVAsset and other classes provide their metadata “lazily” (see AVAsynchronousKeyValueLoading), meaning that you can obtain objects from those arrays without incurring overhead for items you don’t ultimately inspect.
So I'm guessing you won't run into problems of loading the whole file.
Check out the AVMetadataItem API:
let asset = AVAsset(url: url)
assert(asset.isReadable)
let artwork = AVMetadataItem.metadataItems(from: asset.metadata, filteredByIdentifier: .commonIdentifierArtwork).first?.dataValue
let artist = AVMetadataItem.metadataItems(from: asset.metadata, filteredByIdentifier: .commonIdentifierArtist).first?.stringValue
let title = AVMetadataItem.metadataItems(from: asset.metadata, filteredByIdentifier: .commonIdentifierTitle).first?.stringValue
let albumName = AVMetadataItem.metadataItems(from: asset.metadata, filteredByIdentifier: .commonIdentifierAlbumName).first?.stringValue
let duration = asset.duration.seconds