iOS9 FileManager File permissions change - swift

After switching my application to iOS9 I started to get errors that the files I was writing were not readable. Here is how I create the files
let fileManager = NSFileManager.defaultManager()
let directory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let path = "\(directory)/file.txt"
let attributes: [String:AnyObject] = [NSFilePosixPermissions: NSNumber(short: 666)]
let success = fileManager.createFileAtPath(path, contents: nil, attributes: attributes)
if success && fileManager.isWritableFileAtPath(path) && fileManager.isReadableFileAtPath(path) {
NSLog("Worked!")
} else {
NSLog("Failed!")
}
When I do this I keep seeing failed!.

The original code is just wrong. You need to use the octal representation of the permissions:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/swift/grammar/octal-literal
Correct code:
let fileManager = NSFileManager.defaultManager()
let directory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let path = "\(directory)/file.txt"
let attributes: [String:AnyObject] = [NSFilePosixPermissions: NSNumber(short: 0o666)]
let success = fileManager.createFileAtPath(path, contents: nil, attributes: attributes)
if success && fileManager.isWritableFileAtPath(path) && fileManager.isReadableFileAtPath(path) {
NSLog("Worked!")
} else {
NSLog("Failed!")
}
A function I used to test all possible permissions.
func testPermissions() {
let types: [Int16] = [0o666, 0o664, 0o662, 0o660, 0o646, 0o626, 0o606, 0o466, 0o266, 0o066]
for t in types {
testCreateFile(t)
}
}
func testCreateFile(permissions: Int16) {
let attributes: [String:AnyObject] = [NSFilePosixPermissions: NSNumber(short: permissions)]
let directory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let filename = "filename\(permissions.description)"
let path = "\(directory)/\(filename)"
let fileManager = NSFileManager.defaultManager()
let success = fileManager.createFileAtPath(path, contents: nil, attributes: attributes)
if success && fileManager.isWritableFileAtPath(path) && fileManager.isReadableFileAtPath(path) {
let octal = String(format:"%o", permissions)
NSLog("It worked for \(octal)")
}
}

Related

Return file if exists, otherwise return false

I'm trying to make a String extension that searches for a file in my app's directory and either returns that file or returns false if it does not exist. Here's what I have:
extension String {
func doesFileWithNameExist() -> Bool {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let filePath = url.appendingPathComponent(self+".png")?.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath!) {
return true
} else {
return false
}
}
}
Right now my function just returns a Bool, but I'm wondering if there's a way to just return the file if it exists, otherwise return false. Is there a way to return different value types from a function?
It's highly recommended to use the URL related API. This returns an optional UIImage:
extension String {
func doesFileWithNameExist() -> UIImage? { // maybe better pngImageInDocumentsFolder()
let fileManager = FileManager.default
do {
let url = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let fullURL = url.appendingPathComponent(self).appendingPathExtension("png")
_ = try fullURL.checkResourceIsReachable()
let data = try Data(contentsOf: fullURL)
return UIImage(data: data)
} catch {
return nil
}
}
}
or using Leo's great reduction (slightly still reduced):
var image: UIImage? {
guard let url = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(self).appendingPathExtension("‌​png"),
let data = try? Data(contentsOf: url) else { return nil }
return UIImage(data: data)
}
You can return an optional value here, which in Swift is "a thing or nil" – very close to "that file or false." For example, here's a quick tweak to your extension function that returns the path if a file exists there, or nil otherwise:
extension String {
func doesFileWithNameExist() -> String? {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let filePath = url.appendingPathComponent(self+".png")?.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath!) {
return filePath
} else {
return nil
}
}
}
A quick editorial comment, though: this kind of behavior doesn't seem especially well-suited to a String extension. I'd consider writing an extension on FileManager instead, passing in a string for the file's name. Given the hardcoding of the "png" extension, maybe something with the following signature?
extension FileManager {
func pathToExistingPNGFile(named name: String) -> String? {
// …
}
}

instance member "baseURL" cannot be used on type "URL"

On the fifth line i m getting the error that " instance member "baseURL" cannot be used on type "URL". Someone please help me solve this error.
func getFileURL() -> URL {
let fileName = (imagePath! as NSString).lastPathComponent
let dirPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let pathArray:[String] = [dirPath, fileName]
let fileURL = URL.baseURL(withPathComponents: pathArray)
return fileURL!
}
let fileURL = URL.baseURL(withPathComponents: pathArray)
You're calling baseURL on the class URL, not an actual instance of URL. It would be like trying to eat not an apple, but the word "apple". Try this instead:
func getFileURL() -> URL {
let fileName = (imagePath! as NSString).lastPathComponent
let dirPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let fileURL = URL(fileURLWithPath: dirPath, isDirectory: true).appendingPathComponent(fileName)
return fileURL.baseURL!
}
EDIT: I'm not exactly sure about what you're trying to ask, so you may need to change return fileURL.baseURL! to return fileURL.
func getFileURL() -> String {
var paths: [AnyObject] = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
filePath = paths[0].stringByAppendingString("/output/outPut.mov") // whatever filetype you want
return File Path
}

Search for all txt files in directory - Swift

a. How should I get all the txt files in directory?
i got a path of directory and now i should find all the txt files and change every one a little.
i try to run over all the files:
let fileManager = NSFileManager.defaultManager()
let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(folderPath)
while let element = enumerator?.nextObject() as? String {
}
}
but I stuck there. How can I check if the filetype is text?
b. When i get to a directory (in the directory I run), I want get in and search there too, and in the end get out to the place I was and continue.
a is much more important to me but if I get an answer to b too it will be nice.
a. Easy and simple solution for Swift 3:
let enumerator = FileManager.default.enumerator(atPath: folderPath)
let filePaths = enumerator?.allObjects as! [String]
let txtFilePaths = filePaths.filter{$0.contains(".txt")}
for txtFilePath in txtFilePaths{
//Here you get each text file path present in folder
//Perform any operation you want by using its path
}
Your task a is completed by above code.
When talking about b, well you don't have to code for it because we are here using a enumerator which gives you the files which are inside of any directory from your given root directory.
So the enumerator does the work for you of getting inside a directory and getting you their paths.
You can use for .. in syntax of swift to enumerate through NSEnumerator.
Here is a simple function I wrote to extract all file of some extension inside a folder.
func extractAllFile(atPath path: String, withExtension fileExtension:String) -> [String] {
let pathURL = NSURL(fileURLWithPath: path, isDirectory: true)
var allFiles: [String] = []
let fileManager = NSFileManager.defaultManager()
if let enumerator = fileManager.enumeratorAtPath(path) {
for file in enumerator {
if let path = NSURL(fileURLWithPath: file as! String, relativeToURL: pathURL).path
where path.hasSuffix(".\(fileExtension)"){
allFiles.append(path)
}
}
}
return allFiles
}
let folderPath = NSBundle.mainBundle().pathForResource("Files", ofType: nil)
let allTextFiles = extractAllFile(atPath: folder!, withExtension: "txt") // returns file path of all the text files inside the folder
I needed to combine multiple answers in order to fetch the images from a directory and I'm posting my solution in Swift 3
func searchImages(pathURL: URL) -> [String] {
var imageURLs = [String]()
let fileManager = FileManager.default
let keys = [URLResourceKey.isDirectoryKey, URLResourceKey.localizedNameKey]
let options: FileManager.DirectoryEnumerationOptions = [.skipsPackageDescendants, .skipsSubdirectoryDescendants, .skipsHiddenFiles]
let enumerator = fileManager.enumerator(
at: pathURL,
includingPropertiesForKeys: keys,
options: options,
errorHandler: {(url, error) -> Bool in
return true
})
if enumerator != nil {
while let file = enumerator!.nextObject() {
let path = URL(fileURLWithPath: (file as! URL).absoluteString, relativeTo: pathURL).path
if path.hasSuffix(".png"){
imageURLs.append(path)
}
}
}
return imageURLs
}
and here is a sample call
let documentsDirectory = FileManager.default.urls(for:.documentDirectory, in: .userDomainMask)[0]
let destinationPath = documentsDirectory.appendingPathComponent("\(filename)/")
searchImages(pathURL: projectPath)
Swift 4
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let url = URL(fileURLWithPath: documentsPath)
let fileManager = FileManager.default
let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: url.path)!
while let element = enumerator.nextObject() as? String, element.hasSuffix(".txt") {
// do something
}
let fileManager = NSFileManager.defaultManager()
let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(folderPath)
while let element = enumerator?.nextObject() as? String where element.pathExtension == "txt" {
// element is txt file
}
let fileManager = NSFileManager.defaultManager()
let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(folderPath!)!
while let element = enumerator.nextObject() as? String {
if (element.hasSuffix(".txt")) { // element is a txt file }
}

NSFileManager.defaultManager().removeItemAtPath removes file only once app terminates

So I am using the methods bellow to handle getting, deleting, and saving images for my app. When I call removeImage it succeeds and when I use NSFileManager.defaultManager().fileExistsAtPath it says the file does not exist. However, when I call getImage it can still grab the image even though it has been deleted. When I quite the app and start it up again getImage works as expected until I use removeImage. I have also tried wrapping the removeImage call in dispatch_async(dispatch_get_main_queue(),{}) and it still doesn't work.
My code:
class func removeImage(user:User){
let context = getManagedObjectContext()
context.performBlockAndWait({
guard let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true) as [String]! where paths.count > 0 else{
return
}
do{
if let dirPath = paths[0] as String!{
let path = "\(user.id)_\(user.name).png"
let readPath = (dirPath as NSString).stringByAppendingPathComponent(path)
try NSFileManager.defaultManager().removeItemAtPath(readPath)
}
}catch{
}
})
}
class func saveImage(user:User,image:UIImage){
let path = "\(user.id)_\(user.name).png"
guard let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true) as [String]! where paths.count > 0 else{
return
}
if let dirPath = paths[0] as String!{
let writePath = (dirPath as NSString).stringByAppendingPathComponent(path)
UIImagePNGRepresentation(image)!.writeToFile(writePath, atomically: true)
}
}
class func getImage(user:User)->UIImage?{
let path = "\(user.id)_\(user.name).png"
guard let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true) as [String]! where paths.count > 0 else{
return nil
}
if let dirPath = paths[0] as String!{
let readPath = (dirPath as NSString).stringByAppendingPathComponent(path)
return UIImage(named: readPath)
}
return nil
}
UPDATE/FIX! Changed code to this and it worked:
class func removeImage(user:User){
let manager = NSFileManager.defaultManager()
do{
let directoryURL = try manager.URLForDirectory(.DocumentationDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
if #available(iOS 9.0, *) {
let url = NSURL(fileURLWithPath: "\(user.id)_\(user.name).png", relativeToURL: directoryURL)
try manager.removeItemAtURL(url)
} else {
let url = directoryURL.URLByAppendingPathComponent("\(user.id)_\(user.name).png")
try manager.removeItemAtURL(url)
}
}catch{
}
}
class func saveImage(user:User,image:UIImage){
let manager = NSFileManager.defaultManager()
do{
let directoryURL = try manager.URLForDirectory(.DocumentationDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
if #available(iOS 9.0, *) {
let url = NSURL(fileURLWithPath: "\(user.id)_\(user.name).png", relativeToURL: directoryURL)
UIImagePNGRepresentation(image)!.writeToURL(url, atomically: true)
} else {
let url = directoryURL.URLByAppendingPathComponent("\(user.id)_\(user.name).png")
UIImagePNGRepresentation(image)!.writeToURL(url, atomically: true)
}
}catch{
}
}
class func getImage(user:User)->UIImage?{
let manager = NSFileManager.defaultManager()
do{
let directoryURL = try manager.URLForDirectory(.DocumentationDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
if #available(iOS 9.0, *) {
let url = NSURL(fileURLWithPath: "\(user.id)_\(user.name).png", relativeToURL: directoryURL)
if let data = NSData(contentsOfURL: url){
return UIImage(data: data)
}
} else {
let url = directoryURL.URLByAppendingPathComponent("\(user.id)_\(user.name).png")
if let data = NSData(contentsOfURL: url){
return UIImage(data: data)
}
}
}catch{
}
return nil
}

Write and Read a plist in swift with simple data

i'm trying to understand how to save a simple value, an integer, in a plist.
but i'm finding on the net only solution for save dictionary and array and i don't understand what i can change to work it only for an integer.
this is the code for the moment...
var musicalChoice = 1
var musicString : String = "5"
override func viewDidLoad() {
super.viewDidLoad()
musicString = String(musicalChoice)}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func writePlist() {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as NSString
let path = documentsDirectory.stringByAppendingPathComponent("Preferences.plist")
musicString.writeToFile(path, atomically: true, encoding: NSUTF8StringEncoding, error:nil )
}
func readPlist() {
}
Update for Swift 4
I have created SwiftyPlistManager. Take a look at it on GiHub and follow these video instructions:
https://www.youtube.com/playlist?list=PL_csAAO9PQ8bKg79CX5PEfn886SMMDj3j
Update for Swift 3.1
let BedroomFloorKey = "BedroomFloor"
let BedroomWallKey = "BedroomWall"
var bedroomFloorID: Any = 101
var bedroomWallID: Any = 101
func loadGameData() {
// getting path to GameData.plist
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
let documentsDirectory = paths.object(at: 0) as! NSString
let path = documentsDirectory.appendingPathComponent("GameData.plist")
let fileManager = FileManager.default
//check if file exists
if !fileManager.fileExists(atPath: path) {
guard let bundlePath = Bundle.main.path(forResource: "GameData", ofType: "plist") else { return }
do {
try fileManager.copyItem(atPath: bundlePath, toPath: path)
} catch let error as NSError {
print("Unable to copy file. ERROR: \(error.localizedDescription)")
}
}
let resultDictionary = NSMutableDictionary(contentsOfFile: path)
print("Loaded GameData.plist file is --> \(resultDictionary?.description ?? "")")
let myDict = NSDictionary(contentsOfFile: path)
if let dict = myDict {
//loading values
bedroomFloorID = dict.object(forKey: BedroomFloorKey)!
bedroomWallID = dict.object(forKey: BedroomWallKey)!
//...
} else {
print("WARNING: Couldn't create dictionary from GameData.plist! Default values will be used!")
}
}
func saveGameData() {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
let documentsDirectory = paths.object(at: 0) as! NSString
let path = documentsDirectory.appendingPathComponent("GameData.plist")
let dict: NSMutableDictionary = ["XInitializerItem": "DoNotEverChangeMe"]
//saving values
dict.setObject(bedroomFloorID, forKey: BedroomFloorKey as NSCopying)
dict.setObject(bedroomWallID, forKey: BedroomWallKey as NSCopying)
//...
//writing to GameData.plist
dict.write(toFile: path, atomically: false)
let resultDictionary = NSMutableDictionary(contentsOfFile: path)
print("Saved GameData.plist file is --> \(resultDictionary?.description ?? "")")
}
Here's what I use to read/write a plist file in swift:
let BedroomFloorKey = "BedroomFloor"
let BedroomWallKey = "BedroomWall"
var bedroomFloorID: AnyObject = 101
var bedroomWallID: AnyObject = 101
func loadGameData() {
// getting path to GameData.plist
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths[0] as String
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist")
let fileManager = NSFileManager.defaultManager()
//check if file exists
if(!fileManager.fileExistsAtPath(path)) {
// If it doesn't, copy it from the default file in the Bundle
if let bundlePath = NSBundle.mainBundle().pathForResource("GameData", ofType: "plist") {
let resultDictionary = NSMutableDictionary(contentsOfFile: bundlePath)
println("Bundle GameData.plist file is --> \(resultDictionary?.description)")
fileManager.copyItemAtPath(bundlePath, toPath: path, error: nil)
println("copy")
} else {
println("GameData.plist not found. Please, make sure it is part of the bundle.")
}
} else {
println("GameData.plist already exits at path.")
// use this to delete file from documents directory
//fileManager.removeItemAtPath(path, error: nil)
}
let resultDictionary = NSMutableDictionary(contentsOfFile: path)
println("Loaded GameData.plist file is --> \(resultDictionary?.description)")
var myDict = NSDictionary(contentsOfFile: path)
if let dict = myDict {
//loading values
bedroomFloorID = dict.objectForKey(BedroomFloorKey)!
bedroomWallID = dict.objectForKey(BedroomWallKey)!
//...
} else {
println("WARNING: Couldn't create dictionary from GameData.plist! Default values will be used!")
}
}
func saveGameData() {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as NSString
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist")
var dict: NSMutableDictionary = ["XInitializerItem": "DoNotEverChangeMe"]
//saving values
dict.setObject(bedroomFloorID, forKey: BedroomFloorKey)
dict.setObject(bedroomWallID, forKey: BedroomWallKey)
//...
//writing to GameData.plist
dict.writeToFile(path, atomically: false)
let resultDictionary = NSMutableDictionary(contentsOfFile: path)
println("Saved GameData.plist file is --> \(resultDictionary?.description)")
}
The plist file is this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BedroomFloor</key>
<integer>101</integer>
<key>BedroomWall</key>
<integer>101</integer>
<key>XInitializerItem</key>
<string>DoNotEverChangeMe</string>
</dict>
</plist>
My variant function to read and write .plist on swift, tested on device.
Exapmle:
var dataVersion = readPlist("Options", key: "dataVersion")
writePlist("Options", key: "dataVersion", data: 1.23)
Function:
func readPlist(namePlist: String, key: String) -> AnyObject{
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as! NSString
let path = documentsDirectory.stringByAppendingPathComponent(namePlist+".plist")
var output:AnyObject = false
if let dict = NSMutableDictionary(contentsOfFile: path){
output = dict.objectForKey(key)!
}else{
if let privPath = NSBundle.mainBundle().pathForResource(namePlist, ofType: "plist"){
if let dict = NSMutableDictionary(contentsOfFile: privPath){
output = dict.objectForKey(key)!
}else{
output = false
println("error_read")
}
}else{
output = false
println("error_read")
}
}
return output
}
func writePlist(namePlist: String, key: String, data: AnyObject){
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as! NSString
let path = documentsDirectory.stringByAppendingPathComponent(namePlist+".plist")
if let dict = NSMutableDictionary(contentsOfFile: path){
dict.setObject(data, forKey: key)
if dict.writeToFile(path, atomically: true){
println("plist_write")
}else{
println("plist_write_error")
}
}else{
if let privPath = NSBundle.mainBundle().pathForResource(namePlist, ofType: "plist"){
if let dict = NSMutableDictionary(contentsOfFile: privPath){
dict.setObject(data, forKey: key)
if dict.writeToFile(path, atomically: true){
println("plist_write")
}else{
println("plist_write_error")
}
}else{
println("plist_write")
}
}else{
println("error_find_plist")
}
}
}
You can't have anything other than an array or dictionary as the root object in a plist. This is because plist files are essentially special xml files so when you are trying to read the file you ask for object at key or object at index, otherwise you have no means of obtaining your data. Also, when inserting numbers into a plist, you must wrap them in the NSNumber class. To save your objects, check out this answer.