NSFileManager fails to save - No such file or directory - swift

I'm using 'AlamoFireImage' to download my image files.
And after that, I use NSFileManager and CreateFile method to save them in a directory.
But when I try to retrieve the saved images, It only returns the first File. While It should have saved 15 images.
Here's my function for saving Image Files :
func getImage(url: String) {
Alamofire.request("https://www.tandori.ir/uploads/categories/" + url).responseImage { (response) in
if response.result.value != nil {
do {
let fileManager = FileManager.default
let paths = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent(url)
let image = UIImage(named: url)
let imageData = image?.jpegData(compressionQuality: 0.2)
fileManager.createFile(atPath: paths as String, contents: imageData, attributes: nil)
print(paths)
}
catch let error as NSError {
print(error)
}
}
}
}
And this is my function for downloading them :
func launchApp() {
let imgTitiles = UserDefaults.standard.array(forKey: "imgsArray")!
for pic in imgTitiles {
self.getImage(url: pic as! String)
print("done")
}
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let resultViewController = storyBoard.instantiateViewController(withIdentifier: "mainTab") as! TandoriTabView
self.present(resultViewController, animated: true)
}
When I try to retrieve the saved images, I get this error on Console :
"BOMStream BOMStreamWithFileAndSys(int, off_t, size_t, int, char *, BomSys *): read: No such file or directory"
I'm trying to Show these images in a collection view.
Here's my code in my ViewController :
//Get Images Directory Path Function.
func getDirectoryPath() -> String {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
return documentsDirectory
}
//Get Images Function.
func getImage(name : String) -> String {
_ = FileManager.default
let imagePAth = (self.getDirectoryPath() as NSString).appendingPathComponent(name)
return imagePAth
}
//Show Images in collectionView cells function.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = categoriesCollection.dequeueReusableCell(withReuseIdentifier: "categories", for: indexPath) as! categoriesCell
let imageAddress = getImage(name: imgTitiles[indexPath.row] as! String) //TheRealShit
cell.categoryImage.image = UIImage(contentsOfFile: imageAddress)
return cell
}
What seems to be the Problem?
Thanks in advance!

Actually your using Alamofire downloading images are in asynchronously. Due to this you are only few images when you are showing viewcontroller. Modify your code to getImage() synchronously in below code. I hope it will work for you.
func getImage(url: String) {
if let image = UIImage(named: url) {
let fileManager = FileManager.default
let paths = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent(url)
let imageData = image?.jpegData(compressionQuality: 0.2)
fileManager.createFile(atPath: paths as String, contents: imageData, attributes: nil)
print(paths)
}
}

Related

Program on Swift Crashes when first compiling

So I ran this code using swift and the issue that I am having is that it's not working on the first try it compiles the project. However, when I stop the project and run it again it works perfectly fine and I do repeat this several times and the issue doesn't occur anymore. More specifically, when the project opens and I press a button on the app it goes to the error that I show below:
This is the error that I get when I run it for the first time
"Thread 1: Fatal error: 'try!' expression unexpectedly raised an
error: Error Domain=NSCocoaErrorDomain Code=260 "The file “.tmp”
couldn’t be opened because there is no such file."
UserInfo={NSFilePath=/.tmp, NSUnderlyingError=0x60000335d9b0 {Error
Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}"
This error shows up on the line where let contentstring is created. I check the value of contentstring and I get that contentstring = (NSString) 0x0000000000000000. When running the program multiple times it gives me a valid value and it works perfectly fine.
I am not sure why this error occurs only once when the button is pressed and what is a solid approach to this problem.
import UIKit
class KresgePorter: UIViewController, UITableViewDataSource, UITableViewDelegate {
var printString: String!
var array: [String] = []
var count: Int = 0
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: false)
}
override func viewDidLoad() {
super.viewDidLoad()
/*-------------------------------------------------------------------------------------------------------------------------------------------------*/
var nameTXT = ""
var documentsDir = ""
let website = some string url
guard let url1 = URL(string: website) else { return }
//This portion of the code focuses on creating a download task with a completion handler
//Completion handler moves the downloaded file to the app's directory
let downloadTask = URLSession.shared.downloadTask(with: url1) {
urlOrNil, responseOrNil, errorOrNil in
// check for and handle errors:
// * errorOrNil should be nil
// * responseOrNil should be an HTTPURLResponse with statusCode in 200..<299
print("Went into the let\n")
guard let fileURL = urlOrNil else { return }
do {
let documentsURL = try
FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
let savedURL = documentsURL.appendingPathComponent(fileURL.lastPathComponent)
let filename = fileURL.lastPathComponent
let fileName2 = URL(fileURLWithPath: filename).deletingPathExtension().lastPathComponent
nameTXT = fileName2
print("the content of nameTXT is: \(nameTXT)")
try FileManager.default.moveItem(at: fileURL, to: savedURL)
} catch {
print ("file error: \(error)")
}
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
documentsDir = paths.firstObject as! String
print("Path to the Documents directory\n\(documentsDir)")
}
downloadTask.resume()
//If you want to receive progress updates as the download proceeds, you must use a delegate.
var urlSession = URLSession(configuration: .default, delegate: self as? URLSessionDelegate, delegateQueue: nil)
func startDownload(url1: URL){
print("Went into the startDownload function\n")
let downloadTask = urlSession.downloadTask(with: url1)
//let fname = downloadTask.response?.suggestedFilename
downloadTask.resume()
}
startDownload(url1: url1)
/*-----------------------------------------------------------------------------------------------------*/
usleep(270000)
let directPath = documentsDir + "/" + nameTXT + ".tmp"
let url = URL(fileURLWithPath: directPath)
let contentString = try! NSString(contentsOf: url, encoding: String.Encoding.utf8.rawValue)
printString = contentString as String
let fullName = printString
array = fullName!.components(separatedBy: "\n")
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "breakfast", for: indexPath)
cell.textLabel?.text = array[indexPath.row]
return cell
}
}
I shouldn't use try! in production code!
let contentString = try! NSString(contentsOf: url, encoding: String.Encoding.utf8.rawValue)
convert to this:
do {
let contentString = try? String( url, encoding: .utf8)
} catch {
print(error)
}
The actual problem is that:
let directPath = documentsDir + "/" + nameTXT + ".tmp"
let url = URL(fileURLWithPath: directPath)
yields "/.tmp". this is an invalid URL.
This is because of both documentsDir and nameTXT are empty.
So, the core reason is that you call
let directPath = documentsDir + "/" + nameTXT + ".tmp"
let url = URL(fileURLWithPath: directPath)
outside of the server response callback.
Just move your code from
// If you want to receive progress updates as the download until
array = fullName!.components(separatedBy: "\n")
into closure right after this line:
print("Path to the Documents directory\n\(documentsDir)")
your closure is launched asynchronously.

UIImage keeps loading all time when scroll even store in NSCache - swift

I am new in iOS programming. I am creating a simple app which loads image from a particular link ( firestore ). The images are completely downloaded from the server and visible on each cell of collectionview as usual. But the problem is that when when I scroll up or down then those images keeps loading again. I think it starts downloading again because when I turn off internet connection, those images are not being loaded anymore.
Here is how i set images in each cell
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CollectionCell
let explore = dataAppend[indexPath.item]
//cell.imageDisplay.text = explore.title
if let imageUrl = explore.image {
cell.imageDisplay.loadImageWithData(urlString: imageUrl)
}
//print(explore.image)
return cell
}
Here is how loading images look like loadImageWithData(urlString: imageUrl)
let imageCache = NSCache<NSString, UIImage>()
class CustomImageView : UIImageView {
var imageUrlString: String?
func loadImageWithData (urlString: String) {
imageUrlString = urlString
if let imageFromCache = imageCache.object(forKey: urlString as NSString){
self.image = imageFromCache
}
image = nil
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if let err = error {
print(err.localizedDescription)
}
if let data = data {
DispatchQueue.main.async {
let imageToCache = UIImage(data: data)
if self.imageUrlString == urlString {
self.image = imageToCache
}
imageCache.setObject(imageToCache!, forKey: urlString as NSString)
}
}
}).resume()
}
}
var imageCache = NSMutableDictionary()
class CustomImageView: UIImageView {
func loadImageUsingCacheWithUrlString(urlString: String) {
self.image = nil
if let img = imageCache.valueForKey(urlString) as? UIImage{
self.image = img
return
}
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(NSURL(string: urlString)!, completionHandler: { (data, response, error) -> Void in
if(error == nil){
if let img = UIImage(data: data!) {
imageCache.setValue(img, forKey: urlString) // Image saved for cache
DispatchQuee.main.asyn{
self.image = img
}
}
})
task.resume()
}
}
}
You can instead use the Kingfisher library , handles the image caching itself you don't need to worry about it. For implementing see :
https://github.com/onevcat/Kingfisher
with just one line of code you can set the image
imgView.kf.setImage(with: ImageResource(downloadURL: URL(string: imgUrl)!))

saving document path in custom struct array in Userdefaults error swift

I have a custom struct Information. for the property image(string) I want to insert the path of the document directory where the image is saved. When i try to use the UserDefaults to save the struct array, it is saved successfully and also retrieved. But when i use the path to retrieve the image from the document directory it shows the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value.
And when I use the if-else block to catch the exception, No image is displayed on the tableview.
Below is my code:
struct Information{
var image : String
var content : String?
var url : String?
init(image:String,content:String?,url:String?){
self.image = image
self.content = content
self.url = url
}
init(dictionary : [String:String]) {
self.image = dictionary["image"]!
self.content = dictionary["content"]!
self.url = dictionary["url"]!
}
var dictionaryRepresentation : [String:String] {
return ["image" : image, "content" : content!, "url" : url!]
}
}
And my View Controller:
override func viewDidAppear(_ animated: Bool) {
savePath()
loadDefaults()
tableView.reloadData()
}
func saveDefaults()
{
let cfcpArray = information.map{ $0.dictionaryRepresentation }
UserDefaults.standard.set(cfcpArray, forKey: "cfcpArray")
}
func loadDefaults()
{
information = (UserDefaults.standard.object(forKey: "cfcpArray") as! [[String:String]]).map{ Information(dictionary:$0) }
for info in information{
print(info)
}
}
func savePath(){
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
// Get the Document directory path
let documentDirectorPath:String = paths[0]
// Create a new path for the new images folder
imagesDirectoryPath = documentDirectorPath + "/ImagePicker"
var objcBool:ObjCBool = true
let isExist = FileManager.default.fileExists(atPath: imagesDirectoryPath, isDirectory: &objcBool)
// If the folder with the given path doesn't exist already, create it
if isExist == false{
do{
try FileManager.default.createDirectory(atPath: imagesDirectoryPath, withIntermediateDirectories: true, attributes: nil)
}catch{
print("Something went wrong while creating a new folder")
}
}
tableView.reloadData()
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
// The info dictionary may contain multiple representations of the image. You want to use the edited.
guard let selectedImage = info[UIImagePickerControllerEditedImage] as? UIImage
else {
fatalError("Expected a dictionary containing an image, but was provided the following: \(info)")
}
image = selectedImage
dismiss(animated: true, completion: nil)
saveImage()
}
func saveImage(){
// Save image to Document directory
var imagePath = Date().description
imagePath = imagePath.replacingOccurrences(of: " ", with: "")
imagePath = imagesDirectoryPath + "/\(imagePath).png"
path = imagePath
// print(path!)
let data = UIImagePNGRepresentation(image)
let success = FileManager.default.createFile(atPath:path!, contents: data, attributes: nil)
information.append(Information(image:path!, content:" ", url: " "))
saveDefaults()
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "TableViewCell"
/*Because you created a custom cell class that you want to use, downcast the type of the cell to your custom cell subclass, MealTableViewCell.*/
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier , for: indexPath) as? TableViewCell
else{
fatalError("The dequeued cell is not an instance of MealTableViewCell.")
}
let info = information[indexPath.row]
if let data = FileManager.default.contents(atPath: info.image)
{
let decodeimage = UIImage(data: data)
cell.photos.image = decodeimage
}
else{
print("Not displaying image")
}
// cell.photos.image = UIImage(data: data!)
return cell
}
Any suggestions is really appreciated. Thank you.
Don't do that. The URL to the documents folder changes periodically for security reasons.
Save a relative path – or just the file name – and after reading the data get the current documents URL and append the path.
Note: NSSearchPathForDirectoriesInDomains is outdated. Use url(for:in:appropriateFor:create:) and the URL related API of FileManager
PS: Why are content and url optionals although the dictionary initializer passes always non-optional values?
Here is the working code. Tested.
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
}
And to save the image :
// Save image to Document directory
var imagePath = Date().description
imagePath = imagePath.replacingOccurrences(of: " ", with: "")
imagePath = "/\(imagePath).png"
let relpath = getDocumentsURL()
let relativepath = relpath.appendingPathComponent(imagePath)
let data = UIImagePNGRepresentation(image)
let success = FileManager.default.createFile(atPath: relativepath.path , contents: data, attributes: nil)

Save image in Realm

I'm trying to pick image from device's Photo Library in method:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
userPhoto.image = info[UIImagePickerControllerOriginalImage] as! UIImage?
userPhoto.contentMode = .scaleAspectFill
userPhoto.clipsToBounds = true
dismiss(animated: true, completion: nil)
}
and save this picture in Realm (as NSData):
asset.assetImage = UIImagePNGRepresentation(userPhoto.image!)! as NSData?
...
try! myRealm.write
{
user.assetsList.append(asset)
myRealm.add(user)
}
After build succeeded and trying to pick and save image (in the app) i have app error:
'Binary too big'
What i'm doing wrong?
P.S. Sorry for my English :)
After some actions i have this code. But it's overwrite my image.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
let imageUrl = info[UIImagePickerControllerReferenceURL] as! NSURL
let imageName = imageUrl.lastPathComponent
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let photoURL = NSURL(fileURLWithPath: documentDirectory)
let localPath = photoURL.appendingPathComponent(imageName!)
let image = info[UIImagePickerControllerOriginalImage]as! UIImage
let data = UIImagePNGRepresentation(image)
do
{
try data?.write(to: localPath!, options: Data.WritingOptions.atomic)
}
catch
{
// Catch exception here and act accordingly
}
userPhoto.image = image
userPhoto.contentMode = .scaleAspectFill
userPhoto.clipsToBounds = true
urlCatch = (localPath?.path)!
self.dismiss(animated: true, completion: nil);
}
Don't save the image itself into realm, just save the location of the image into realm as String or NSString and load the image from that saved path. Performance wise it's always better to load images from that physical location and your database doesn't get too big
func loadImageFromPath(_ path: NSString) -> UIImage? {
let image = UIImage(contentsOfFile: path as String)
if image == nil {
return UIImage()
} else{
return image
}
}
or you just save the image name, if it's in your documents directory anyhow
func loadImageFromName(_ imgName: String) -> UIImage? {
guard imgName.characters.count > 0 else {
print("ERROR: No image name")
return UIImage()
}
let imgPath = Utils.getDocumentsDirectory().appendingPathComponent(imgName)
let image = ImageUtils.loadImageFromPath(imgPath as NSString)
return image
}
and here a rough example how to save a captured image to your directory with a unique name:
#IBAction func capture(_ sender: AnyObject) {
let videoConnection = stillImageOutput?.connection(withMediaType: AVMediaTypeVideo)
stillImageOutput?.captureStillImageAsynchronously(from: videoConnection, completionHandler: { (imageDataSampleBuffer, error) -> Void in
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)
//self.stillImage = UIImage(data: imageData!)
//self.savedImage.image = self.stillImage
let timestampFilename = String(Int(Date().timeIntervalSince1970)) + "someName.png"
let filenamePath = URL(fileReferenceLiteralResourceName: getDocumentsDirectory().appendingPathComponent(timestampFilename))
let imgData = try! imageData?.write(to: filenamePath, options: [])
})
/* helper get Document Directory */
class func getDocumentsDirectory() -> NSString {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
//print("Path: \(documentsDirectory)")
return documentsDirectory as NSString
}
https://realm.io/docs/objc/latest/#current-limitations
maximum data size is 16 MB . this is limitation of realm
Depending on how your serializing the image data (for example if it's a lossless bitmap), it's quite possible that this data exceed 16MB, which as you've stated is Realm's maximum supported size for binary properties.
When you invoke NSData.length, how large does it say your data is?

Setting Alamofire custom destination file name instead of using suggestedDownloadDestination

I got many lists of invoice file at my table view as well as many download buttons at each cell.When I clicked one of it,it will download the invoice file.But,the problem is the server response suggested file name is "invoice.pdf" at every file I downloaded.So,I need to edit the file name manually before I save to document after it was downloaded.So,how to edit the file name manually after it was download successfully and save it in document as temporaryurl without using Alamofire.Request.suggestedDownloadDestination.
Here is my download function.
func downloadInvoice(invoice: Invoice, completionHandler: (Double?, NSError?) -> Void) {
guard isInvoiceDownloaded(invoice) == false else {
completionHandler(1.0, nil) // already have it
return
}
let params = [
"AccessToken" : “xadijdiwjad12121”]
// Can’t use the destination file anymore because my server only return one file name “invoice.pdf” no matter which file i gonna download
// So I have to manually edit my file name which i saved after it was downloaded.
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
// So I have to save file name like that ““2016_04_02_car_invoice_10021.pdf” [Date_car_invoice_timestamp(Long).pdf]
// Please look comment on tableView code
Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders?.updateValue("application/pdf",forKey: "Content-Type")
Alamofire.download(.POST, invoice.url,parameters:params, destination: destination)
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
print(totalBytesRead)
dispatch_async(dispatch_get_main_queue()) {
let progress = Double(totalBytesRead) / Double(totalBytesExpectedToRead)
completionHandler(progress, nil)
}
}
.responseString { response in
print(response.result.error)
completionHandler(nil, response.result.error)
}
}
Here is the table view which gonna check downloaded file and when it click,shown on open in feature.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let invoice = dataController.invoices?[indexPath.row] {
dataController.downloadInvoice(invoice) { progress, error in
// TODO: handle error
print(progress)
print(error)
if (progress < 1.0) {
if let cell = self.tableView.cellForRowAtIndexPath(indexPath), invoiceCell = cell as? InvoiceCell, progressValue = progress {
invoiceCell.progressBar.hidden = false
invoiceCell.progressBar.progress = Float(progressValue)
invoiceCell.setNeedsDisplay()
}
}
if (progress == 1.0) {
// Here where i gonna get the downloaded file name from my model.
// invoice.filename = (Assume “2016_04_02_car_invoice_10021”)
if let filename = invoice.filename{
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let docs = paths[0]
let pathURL = NSURL(fileURLWithPath: docs, isDirectory: true)
let fileURL = NSURL(fileURLWithPath: filename, isDirectory: false, relativeToURL: pathURL)
self.docController = UIDocumentInteractionController(URL: fileURL)
self.docController?.delegate = self
if let cell = self.tableView.cellForRowAtIndexPath(indexPath) {
self.docController?.presentOptionsMenuFromRect(cell.frame, inView: self.tableView, animated: true)
if let invoiceCell = cell as? InvoiceCell {
invoiceCell.accessoryType = .Checkmark
invoiceCell.setNeedsDisplay()
}
}
}
}
}
}
}
So,my question is simple.I just don't want to use that code
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
because it use response.suggestedfilename.And I want to save file name manually on selected table view cell data.Any Help?Please don't mind that I posted some code in my question because I want everyone to see it clearly.
Destination is of type (NSURL, NSHTTPURLResponse) -> NSURL. so you can do something like this
Alamofire.download(.POST, invoice.url,parameters:params, destination: { (url, response) -> NSURL in
let pathComponent = "yourfileName"
let fileManager = NSFileManager.defaultManager()
let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let fileUrl = directoryURL.URLByAppendingPathComponent(pathComponent)
return fileUrl
})
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
print(totalBytesRead)
dispatch_async(dispatch_get_main_queue()) {
let progress = Double(totalBytesRead) / Double(totalBytesExpectedToRead)
completionHandler(progress, nil)
}
}
.responseString { response in
print(response.result.error)
completionHandler(nil, response.result.error)
}
}
Swift 3.0
in swift 3.0 it's DownloadFileDestination
Alamofire.download(url, method: .get, to: { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
return (filePathURL, [.removePreviousFile, .createIntermediateDirectories])
})
.downloadProgress(queue: utilityQueue) { progress in
print("Download Progress: \(progress.fractionCompleted)")
}
.responseData { response in
if let data = response.result.value {
let image = UIImage(data: data)
}
}
for more go to the Alamofire