How do I write a file in iOS using FileManager? - ios

I have the following code with the results of the print statements in comments beside the statements:
let myImageData = FileManager.default.contents(atPath: myURL.absoluteString)
var mySaveToURL: URL = FileManager.default.url(forUbiquityContainerIdentifier: nil)!
mySaveToURL.appendPathComponent(myURL.pathComponents.last!)
print("mySaveToURL=", mySaveToURL) // mySaveToURL= file:///Users/shinehah/Library/Developer/CoreSimulator/Devices/693D4940-1B91-43E1-B5AD-88E9763046C7/data/Library/Mobile%20Documents/iCloud~us~gnolaum~TrialNotifications/ABF236AE-A6E7-403E-ADC4-5BAA5DC734B3.jpeg
let resultCreateFile = FileManager.default.createFile(atPath: mySaveToURL.absoluteString, contents: myImageData, attributes: nil)
print("resultCreateFile=", resultCreateFile) // resultCreateFile= false
do {
try FileManager.default.copyItem(at: myURL, to: mySaveToURL)
print("copy success!") // copy success!
} catch {
print(error.localizedDescription)
}
As you can see I am not able to successfully execute the createFile() method of FileManager but was able to successfully execute the copyItem() method to the same URL.
What do I check to be able to figure out how to get the createFile() method to work?

The error occurs because you are using the wrong API. To get the path of a file system URL you have to use path.
let resultCreateFile = FileManager.default.createFile(atPath: mySaveToURL.path, contents: myImageData, attributes: nil)
However there is no reason to create the file explicitly. Just copy the other file.

Related

Get the names of files in an iCloud Drive folder that haven't been downloaded yet

I’m trying to get the names of all files and folders in an iCloud Drive directory:
import Foundation
let fileManager = FileManager.default
let directoryURL = URL(string: "folderPathHere")!
do {
let directoryContents = try fileManager.contentsOfDirectory(at: directoryURL, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
for url in directoryContents {
let fileName = fileManager.displayName(atPath: url.absoluteString)
print(fileName)
}
} catch let error {
let directoryName = fileManager.displayName(atPath: directoryURL.absoluteString)
print("Couldnt get contents of \(directoryName): \(error.localizedDescription)")
}
It appears that any iCloud files that haven’t been downloaded to the device don’t return URLs.
I know I can check if a path contains a ubiquitous item when I already know the path with the code below (even if it isn’t downloaded):
fileManager.isUbiquitousItem(at: writePath)
Is there a way to get the URLs & names of those iCloud files without downloading them first?
The directory URL is a security-scoped URL constructed from bookmark data in case that makes any difference (omitted that code here for clarity).
Thanks
Found the answer. I was skipping hidden files with ".skipsHiddenFiles", but the non-downloaded files are actually hidden files, named: ".fileName.ext.iCloud".
Remove the skips hidden files option now works as expected.
You need to use a NSFileCoordinator to access the directory in iCloud Storage, and then normalize placeholder file names for items that haven't been downloaded yet:
let iCloudDirectoryURL = URL(...)
let fileCoordinator = NSFileCoordinator(filePresenter: nil)
fileCoordinator.coordinate(
readingItemAt: iCloudDirectoryURL,
options: NSFileCoordinator.ReadingOptions(),
error: nil
) { readingURL in
do {
let contents = try FileManager.default.contentsOfDirectory(
at: readingURL, includingPropertiesForKeys: nil
)
for url in contents {
print("\(canonicalURL(url))")
}
} catch {
print("Error listing iCloud directory: '\(error)'")
}
}
func canonicalURL(_ url: URL) -> URL {
let prefix = "."
let suffix = ".icloud"
var fileName = url.lastPathComponent
if fileName.hasPrefix(prefix), fileName.hasSuffix(suffix) {
fileName.removeFirst(prefix.count)
fileName.removeLast(suffix.count)
var result = url.deletingLastPathComponent()
result.append(path: fileName)
return result
} else {
return url
}
}

Swift File Download Issue

I am trying to download a plist file from a remote location and use it in the iOS app I am creating. The file is going to be used for calendar details within the app's calendar. The goal is obviously that I can update the remote file instead of having to push updates to the app itself every time we need to make changes to calendar details.
I started with the code used in this example: Download File From A Remote URL
Here is my modified version:
// Create destination URL
let documentsUrl:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL!
let destinationFileUrl = documentsUrl.appendingPathComponent("2017.plist")
//let destinationFileUrl = URL(string: Bundle.main.path(forResource: String(currentYear), ofType: "plist")!)
//Create URL to the source file you want to download
let fileURL = URL(string: "https://drive.google.com/open?id=0BwHDQFwaL9DuLThNYWwtQ1VXblk")
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL!)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try FileManager.default.removeItem(at: destinationFileUrl)
try FileManager.default.moveItem(at: tempLocalUrl, to: destinationFileUrl)
print("File was replaced")
print(NSArray(contentsOf: tempLocalUrl))
//print(tempLocalUrl)
} catch (let writeError) {
print("Error creating a file \(String(describing: destinationFileUrl)) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription as Any);
}
}
task.resume()
I originally tried to overwrite the file that is bundled with the app to being with, that resulted in errors. So I instead tried to just save it in the app's documents folder and that removed that error. I had to make sure and remove any previous version of the file because it was giving me a file already exists error after the first run.
While it says everything is working (The outputs for both successful download and replaced file happen) when I print the contents of the array from the downloaded URL it just gives me nil.
This is my first attempt to use any kind of external resources in an app. Before I have always kept everything internal, so I am sure there is something glaringly obvious I am missing.
Update 1:
I realized I didn't have the correct URL to use to download a file from a Google drive. That line of code has been changed to:
let fileURL = URL(string: "https://drive.google.com/uc?export=download&id=0BwHDQFwaL9DuLThNYWwtQ1VXblk")
So now I actually am downloading the plist like I originally thought I was. Even removing the deletion issue mentioned in the first comment, I still can't get the downloaded file to actually replace the existing one.
Update 2:
I have reduced the actual file manipulation down to the following:
do {
try FileManager.default.replaceItemAt(destinationFileUrl, withItemAt: tempLocalUrl)
print("File was replaced")
print(NSArray(contentsOf: destinationFileUrl))
} catch (let writeError) {
print("Error creating a file \(String(describing: destinationFileUrl)) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription as Any);
}
After the replacement is performed the output of the file shows the correct new contents that were downloaded from the internet.
Later in the code when I try and access the file it seems to be nil in content again.
Look at your download completion code. You:
Delete the file at the destination URL (in case there was one
leftover)
MOVE the temp file to the destination URL (removing it from the temp
URL)
Try to load the file from the temp URL.
What's wrong with this picture?
You are trying to get the contents of the moved file. You already moved the file to destination url and then you are trying to get the contents of the file from temporary location.
For getting file data, Please try the following :
let fileData = try! String(contentsOf: destinationFileUrl, encoding: String.Encoding.utf8)
print(fileData)

UNNotificationAttachment failing to attach image

So the following code is being used to attach an image from local storage url of an image. I check in Terminal to see if the image is stored and it does store the image without any issues. So ruling out any issues with the url itself.
do {
let attachment = try UNNotificationAttachment(identifier: imageTag, url: url, options: nil)
content.attachments = [attachment]
} catch {
print("The attachment was not loaded.")
}
Other code that goes with the creation of UserNotification works fine as it triggers at the correct specified time.
The code always goes to the catch block. Can anybody please point me to the mistake if there is any in the implementation. Please help. Thank you.
Edit: with print(error.localizedDescription) error message is Invalid attachment file URL.
Edit2 : with print(error) error message is Error Domain=UNErrorDomain Code=100 "Invalid attachment file URL" UserInfo={NSLocalizedDescription=Invalid attachment file URL}
I have found the real issue behind it. In apple documentation it is written that the url should be a file url and because of which you might be facing issue.
To solve this I have added image to temporary directory and then added to UNNotificationAttachment .
Please find the code below. (For my use case I was getting an imageURL)
extension UNNotificationAttachment {
/// Save the image to disk
static func create(imageFileIdentifier: String, data: NSData, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
let tmpSubFolderURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
do {
try fileManager.createDirectory(at: tmpSubFolderURL!, withIntermediateDirectories: true, attributes: nil)
let fileURL = tmpSubFolderURL?.appendingPathComponent(imageFileIdentifier)
try data.write(to: fileURL!, options: [])
let imageAttachment = try UNNotificationAttachment.init(identifier: imageFileIdentifier, url: fileURL!, options: options)
return imageAttachment
} catch let error {
print("error \(error)")
}
return nil
}
}
data in the argument of this function is Data of image . Below is how did I call this method.
let imageData = NSData(contentsOf: url)
guard let attachment = UNNotificationAttachment.create(imageFileIdentifier: "img.jpeg", data: imageData!, options: nil) else { return }
bestAttemptContent?.attachments = [attachment]
I also found important and quite weird behaviour of initialization of UNNotificationAttachment object. It was happening to me that I was getting error:
"Invalid attachment file URL"
But it was not happening always. It was happening in case when I used for some notifications same image for attachment. When I made a standalone copy of image for each attachment, it never happened. Then I checked directory when images should be copied ( because I wanted to clean it up ), but I was surprised that there were no images.
It seems that UNNotificationAttachment initialization process is deleting files at given URLs. So when you try to reuse some images, they can be deleted ( probably asynchronously, because I was checking existence of that images and it always returned me true - that file exists ). But UNNotificationAttachment ended up with error you can see above. In my opinion only logic explanation of this error is that file at given URL is deleted during the process of UNNotificationAttachment initialization.
Apple actually makes a statement in their documentation (https://developer.apple.com/documentation/usernotifications/unnotificationattachment)
Apple Docs for UNNotificationAttachment:
...
The system validates attachments before displaying the associated notification.
...
Once validated, attached files are MOVED into the attachment data store so that they can be accessed by all of the appropriate
processes. Attachments located inside an app’s bundle are copied
instead of moved.
So, above answers with copying an attachment (image) first into a temporary location before adding as an attachment seem to be the expected solution.
In Swift 5. The below code works for me. Hope this helps somebody.
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
let imageURL = URL(fileURLWithPath: paths.first!).appendingPathComponent("\(fileName).jpg")
let image = UIImage(contentsOfFile: imageURL.path)
let imageData = image?.pngData()
if let unwrappedImageData = imageData, let attachement = try? UNNotificationAttachment(data: unwrappedImageData, options: nil) {
content.attachments = [ attachement ]
}

FileManager.createDirectory fails with NSCocoaErrorDomain Code: 518

I'm doing
let tempDirectory = URL(string: "\(NSTemporaryDirectory())video/")!
do {
try FileManager.default.createDirectory(
at: tempDirectory,
withIntermediateDirectories: true)
} catch { report(error) }
and that's often throwing an NSCocoaErrorDomain Code: 518.
Any idea of the reason? I thought that could because there's already something there, so I added
var isDir: ObjCBool = false
if FileManager.default.fileExists(
atPath: tempDirectory.absoluteString,
isDirectory: &isDir
) {
if isDir.boolValue {
print("Temp directory exists on launch")
}
else {
print("Temp directory exists on launch and is a file")
}
return
}
but that doesn't seem to catch anything
Your building of tempDirectory isn't correct. You want:
let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory()). appendingPathComponent("video")
The issue with your code is that you were not passing a value URL string to URL(string:). Since you have a file path you need to use URL(fileURLWithPath:). And build paths/URLs using the provided methods to ensure slashes and other parts are added correctly.
Print your value of tempDirectory from your original code and then print the new value from the code in my answer. Note the key difference.
Your URL will be something like:
/var/...
and it may be missing the slash before "video".
The correct file URL will be something like:
file:///var/...

Uploading a file from AVCapture using AFNetworking

I have a video that is captured with AVCapture, and I'm trying to upload with AFNetworking with Swift.
Code:
let manager = AFHTTPRequestOperationManager()
let url = "http://localhost/test/upload.php"
var fileURL = NSURL.fileURLWithPath(string: ViewControllerVideoPath)
var params = [
"familyId":locationd,
"contentBody" : "Some body content for the test application",
"name" : "the name/title",
"typeOfContent":"photo"
]
manager.POST( url, parameters: params,
constructingBodyWithBlock: { (data: AFMultipartFormData!) in
println("")
var res = data.appendPartWithFileURL(fileURL, name: "fileToUpload", error: nil)
println("was file added properly to the body? \(res)")
},
success: { (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) in
println("Yes thies was a success")
},
failure: { (operation: AFHTTPRequestOperation!, error: NSError!) in
println("We got an error here.. \(error.localizedDescription)")
})
The code above fails, I keep getting
was file added properly to the body? false"
note that ViewControllerVideoPath is a string containing the location of the video which is:
"/private/var/mobile/Containers/Data/Application/1110EE7A-7572-4092-8045-6EEE1B62949/tmp/movie.mov"
using println().... The code above works when I'm uploading a file included in the directory and using:
var fileURL = NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("test_1", ofType: "mov")!)
So definitely my PHP code is fine, and the problem lies with uploading that file saved on the device, what am I doing wrong here?
Comments don't allow a full explanation so here is more info;
NSBundle.mainBundle() refers to a path in the bundle file The path in the simulator differs from that of the application ... this is not what you want. There are a number of "folders" you can access based on your needs (private or sharable/files that can get backed up to the cloud). NSPathUtils.h gives a breakdown of the paths available. In keeping with conventions used by most, you should probably create a private path under your application path by doing something like;
- (NSURL *) applicationPrivateDocumentsDirectory{
NSURL *pathURL = [[self applicationLibraryDirecory]URLByAppendingPathComponent:#"MyApplicationName"];
return pathURL;
}
- (NSURL *) applicationLibraryDirecory{
return [[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
}
You can test if it exists, if not, create it ... then store your video files in this path, and pass this to your AVCapture as the location to store the file.
Here are the code that can do following functionality in swift.
1 : Check weather directory exist or not. if not exist then create directory(Directory has given application name) in document directory folder.
2 : Now we have application directory. so all file that from application will write/read in/from this directory.
let file = "file.txt"
let directoryName = “XYZ” // Here “XYZ” is project name.
var error : NSError?
let filemgr = NSFileManager.defaultManager()
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,
.UserDomainMask, true)
let documentsDirectory = dirPaths[0] as! String
var dataPath = documentsDirectory.stringByAppendingPathComponent(directoryName)
if !NSFileManager.defaultManager().fileExistsAtPath(dataPath) {
NSFileManager.defaultManager().createDirectoryAtPath(dataPath, withIntermediateDirectories: false, attributes: nil, error: &error)
} else {
println("not creted or exist")
}
Now we have Directory so only need to write/read data from directory.
how to write file in document directory in swift
let filePath = dataPath.stringByAppendingPathComponent(file);
let text = "some text"
//writing
text.writeToFile(filePath, atomically: false, encoding: NSUTF8StringEncoding, error: nil);
How to read file from document directory.
let filePath = dataPath.stringByAppendingPathComponent(file);
// Read file
let text2 = String(contentsOfFile: filePath, encoding: NSUTF8StringEncoding, error: nil)
Output :
Hope this will help you.

Resources