Can't get plist URL in Swift - swift

I'm really confused on this one. There are dozens of questions around the web asking "How do I get info from my plist file in Swift?" and the same answer is posted everywhere:
let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist")
However, this line produces always produces nil for me. I have replaced Config with other components found in the default plist file, but get nil as well.
I am trying to access my custom ProductIdentifiers Array like so:
let url = NSBundle.mainBundle().URLForResource("ProductIdentifiers", withExtension: "plist")!
var productArray = NSArray(contentsOfURL: url) as! [[String:AnyObject!]]
I get a crash stating fatal error: unexpectedly found nil while unwrapping an Optional value on productArray. I have also tried this with other default plist values in place of ProductIdentifiers.
Does anyone know why this is not working for me even though there are so many posts around of people using this successfully?

I've never heard of the OP's approach working before. Instead, you should open the Info.plist file itself, then extract values from it, like so:
Swift 3.0+
func getInfoDictionary() -> [String: AnyObject]? {
guard let infoDictPath = Bundle.main.path(forResource: "Info", ofType: "plist") else { return nil }
return NSDictionary(contentsOfFile: infoDictPath) as? [String : AnyObject]
}
let productIdentifiers = getInfoDictionary()?["ProductIdentifiers"]
Swift 2.0
func getInfoDictionary() -> NSDictionary? {
guard let infoDictPath = NSBundle.mainBundle().pathForResource("Info", ofType: "plist") else { return nil }
return NSDictionary(contentsOfFile: infoDictPath)
}
let productIdentifiers = getInfoDictionary()?["ProductIdentifiers"]

Resource represents the file name of the plist rather than its contents.
The root object of the plist is probably a dictionary.
Replace MyPlist with the real file name.
This code prints the contents of the plist
if let url = NSBundle.mainBundle().URLForResource("MyPlist", withExtension: "plist"),
root = NSDictionary(contentsOfURL: url) as? [String:AnyObject]
{
print(root)
} else {
print("Either the file does not exist or the root object is an array")
}

Related

SWIFT writing to plist is not updating

I'm trying to write to the plist and I'm using two approaches but none of them work for me.
I'm not getting any errors though and when I print the paths I can see that plist exist, however you can see from the screenshot that the plist it is not getting updated/populated.
let path = Bundle.main.path(forResource: "Employee", ofType: "plist")!
let data : NSDictionary =
["A": [["userid":"1","username":"AAA","usergroupid":"2"], ["userid":"33","username":"ABB","usergroupid":"8"]],
"B": [["userid":"2","username":"BBB","usergroupid":"8"], ["userid":"43","username":"ABC","usergroupid":"8"]] ]
//first approach
let favoritesDictionary = NSDictionary(object: data, forKey: ("Favorites" as NSString?)!)
print(path)
let succeeded = favoritesDictionary.write(toFile: path, atomically: true)
//second approach
let bundlePath = Bundle.main.path(forResource: "Employee", ofType: "plist")!
print(bundlePath)
let dictionary = NSMutableDictionary(contentsOfFile: bundlePath)
dictionary?.setObject(data, forKey: ("Locations" as NSString?)!)
dictionary?.write(toFile: bundlePath, atomically: true)
Can someone please help?
This is a short tutorial.
Create your plist file and put it in the application bundle.
In AppDelegate create a computed property to get the current Documents folder and append the file path
var employeePlistURL : URL {
let documentsFolderURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
return documentsFolderURL.appendingPathComponent("Employee.plist")
}
In AppDelegate applicationWillFinishLaunching register a key-value pair for the firstLaunch flag in UserDefaults and copy the plist into the documents folder if the flag is true
func applicationWillFinishLaunching(_ aNotification: Notification) {
let defaults = UserDefaults.standard
defaults.register(defaults: ["firstLaunch":true])
if defaults.bool(forKey: "firstLaunch") {
let sourceFile = Bundle.main.url(forResource: "Employee", withExtension: "plist")!
try? FileManager.default.copyItem(at: sourceFile, to: employeePlistURL)
defaults.set(false, forKey: "firstLaunch")
}
}
Wherever you need to read and write the property list create also the computed property and add a property for the dictionary
var employees = [String:Any]()
and two methods to load and save the data
func loadEmployees() {
do {
let data = try Data(contentsOf: employeePlistURL)
guard let plist = try PropertyListSerialization.propertyList(from: data, format: nil) as? [String:Any] else { return }
employees = plist
} catch { print(error) }
}
func saveEmployees() {
do {
let data = try PropertyListSerialization.data(fromPropertyList: employees, format: .binary, options: 0)
try data.write(to: employeePlistURL)
} catch { print(error) }
}
A better way is to use structs and PropertyListEncoder/-Decoder but as the literal dictionary and the screenshot in the question are rather different I provide the common Dictionary / PropertyListSerialization way.

Path to a file in the Extensions Folder

I am trying to find a path to file in the extensions folder, but unfortunately, the app crashes every time while doing this. I really have no clue as to why its happening. Any suggestions?
The file is located in the Keyboard Folder and its called StickersList.plist
The code is being generated in the viewDidLoad() inside KeyboardViewController.swift
if let path = Bundle.main.path(forResource: "StickersList", ofType: ".plist") {
print("I got in")
print(path)
let dict = NSDictionary(contentsOfFile: path) as! Dictionary<String,AnyObject>
let allSections = dict["Sections"] as? [[String:AnyObject]]
print(allSections)
if let selectedSections = UserDefaults.standard.array(forKey: "selectedSections") as? [Int] {
for index in selectedSections {
self.data.append((allSections![index]))
}
}
}
it crashes as soon as it runs the if statement. Any suggestions?

Argument labels '(contentsOfFile:)' do not match any available overloads

I'm updating my app with the new iOS 11 standards and a lot of stuff were deprecated and now I'm stacked with this error: "Argument labels '(contentsOfFile:)' do not match any available overloads.
Here you are the codes that was working:
//load plist file
var palermoip: NSArray?
if let path = Bundle.main.path(forResource: "palermoip", ofType: "plist") {
palermoip = NSArray(contentsOfFile: path)
}
Anyone knows how can I fix it? Thank you in advance !
I recommend to use PropertyListSerialization and the URL related API
let url = Bundle.main.url(forResource: "palermoip", withExtension: "plist")!
let data = try! Data(contentsOf:url)
let palermoip = try! PropertyListSerialization.propertyList(from: data, format: nil) as! [[String:Any]] // or [Any] if the array does not contain dictionaries
and in Swift 4 even PropertyListDecoder

Swift NSDirectoryEnumerator Generator [duplicate]

I'm quite new to programming a Swift and I'm trying to iterate through the files in a folder.
I took a look at the answer here and tried to translate it to Swift syntax, but didn't succeed.
let fileManager = NSFileManager.defaultManager()
let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(folderPath)
for element in enumerator {
//do something
}
the error I get is:
Type 'NSDirectoryEnumerator' does not conform to protocol 'SequenceType'
My aim is to look at all the subfolders and files contained into the main folder and find all the files with a certain extension to then do something with them.
Use the nextObject() method of enumerator:
while let element = enumerator?.nextObject() as? String {
if element.hasSuffix("ext") { // checks the extension
}
}
Nowadays (early 2017) it's highly recommended to use the – more versatile – URL related API
let fileManager = FileManager.default
do {
let resourceKeys : [URLResourceKey] = [.creationDateKey, .isDirectoryKey]
let documentsURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let enumerator = FileManager.default.enumerator(at: documentsURL,
includingPropertiesForKeys: resourceKeys,
options: [.skipsHiddenFiles], errorHandler: { (url, error) -> Bool in
print("directoryEnumerator error at \(url): ", error)
return true
})!
for case let fileURL as URL in enumerator {
let resourceValues = try fileURL.resourceValues(forKeys: Set(resourceKeys))
print(fileURL.path, resourceValues.creationDate!, resourceValues.isDirectory!)
}
} catch {
print(error)
}
I couldn't get pNre's solution to work at all; the while loop just never received anything. However, I did come across this solution which works for me (in Xcode 6 beta 6, so perhaps things have changed since pNre posted the above answer?):
for url in enumerator!.allObjects {
print("\((url as! NSURL).path!)")
}
my two cents from previously anwers.. more swifty and with optionals:
let enumerator = FileManager.default.enumerator(atPath: folderPath)
while let element = enumerator?.nextObject() as? String {
print(element)
if let fType = enumerator?.fileAttributes?[FileAttributeKey.type] as? FileAttributeType{
switch fType{
case .typeRegular:
print("a file")
case .typeDirectory:
print("a dir")
}
}
}
returns all files in a directory + in subdirectories
import Foundation
let path = "<some path>"
let enumerator = FileManager.default.enumerator(atPath: path)
while let filename = enumerator?.nextObject() as? String {
print(filename)
}
Swift3 + absolute urls
extension FileManager {
func listFiles(path: String) -> [URL] {
let baseurl: URL = URL(fileURLWithPath: path)
var urls = [URL]()
enumerator(atPath: path)?.forEach({ (e) in
guard let s = e as? String else { return }
let relativeURL = URL(fileURLWithPath: s, relativeTo: baseurl)
let url = relativeURL.absoluteURL
urls.append(url)
})
return urls
}
}
Based on code from #user3441734
Swift 3
let fd = FileManager.default
fd.enumerator(atPath: "/Library/FileSystems")?.forEach({ (e) in
if let e = e as? String, let url = URL(string: e) {
print(url.pathExtension)
}
})
In case that you are getting the
'NSDirectoryEnumerator?' does not have a member named 'nextObject' error
the while loop should be:
while let element = enumerator?.nextObject() as? String {
// do things with element
}
It has something to do with optional chaining
SWIFT 3.0
Returns all files with extension in the Directory passed & its subdirectories
func extractAllFile(atPath path: String, withExtension fileExtension:String) -> [String] {
let pathURL = NSURL(fileURLWithPath: path, isDirectory: true)
var allFiles: [String] = []
let fileManager = FileManager.default
let pathString = path.replacingOccurrences(of: "file:", with: "")
if let enumerator = fileManager.enumerator(atPath: pathString) {
for file in enumerator {
if #available(iOS 9.0, *) {
if let path = NSURL(fileURLWithPath: file as! String, relativeTo: pathURL as URL).path, path.hasSuffix(".\(fileExtension)"){
let fileNameArray = (path as NSString).lastPathComponent.components(separatedBy: ".")
allFiles.append(fileNameArray.first!)
}
} else {
// Fallback on earlier versions
print("Not available, #available iOS 9.0 & above")
}
}
}
return allFiles
}
Updating for Swift 3:
let fileManager = FileManager() // let fileManager = NSFileManager.defaultManager()
let en=fileManager.enumerator(atPath: the_path) // let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(folderPath)
while let element = en?.nextObject() as? String {
if element.hasSuffix("ext") {
// do something with the_path/*.ext ....
}
}
Adding to vadian's response -- the Apple docs mention that Path-based URLs are simpler in some ways, however file reference URLs have the advantage that the reference remains valid if the file is moved or renamed while your app is running.
From the documentation for "Accessing Files and Directories":
"Path-based URLs are easier to manipulate, easier to debug, and are generally preferred by classes such as NSFileManager. An advantage of file reference URLs is that they are less fragile than path-based URLs while your app is running. If the user moves a file in the Finder, any path-based URLs that refer to the file immediately become invalid and must be updated to the new path. However, as long as the file moved to another location on the same disk, its unique ID does not change and any file reference URLs remain valid."
https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html
If you want to categorically check whether an element is a file or a subdirectory:
let enumerator = FileManager.default.enumerator(atPath: contentsPath);
while let element = enumerator?.nextObject() as? String {
if(enumerator?.fileAttributes?[FileAttributeKey.type] as! FileAttributeType == FileAttributeType.typeRegular){
//this is a file
}
else if(enumerator?.fileAttributes?[FileAttributeKey.type] as! FileAttributeType == FileAttributeType.typeDirectory){
//this is a sub-directory
}
}
Recently struggled with this when handling an array of urls, whether they be a directory or not (eg. drag and drop). Ended up with this extension in swift 4, may be of use
extension Sequence where Iterator.Element == URL {
var handleDir: [URL] {
var files: [URL] = []
self.forEach { u in
guard u.hasDirectoryPath else { return files.append(u.resolvingSymlinksInPath()) }
guard let dir = FileManager.default.enumerator(at: u.resolvingSymlinksInPath(), includingPropertiesForKeys: nil) else { return }
for case let url as URL in dir {
files.append(url.resolvingSymlinksInPath())
}
}
return files
}
}
Avoid reference URLs, while they do have some advantages as stated above, they eat system resources and if you’re enumerating a large filesystem (not that large actually) your app will hit a system wall quickly and get shutdown by macOS.

Iterate through files in a folder and its subfolders using Swift's FileManager

I'm quite new to programming a Swift and I'm trying to iterate through the files in a folder.
I took a look at the answer here and tried to translate it to Swift syntax, but didn't succeed.
let fileManager = NSFileManager.defaultManager()
let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(folderPath)
for element in enumerator {
//do something
}
the error I get is:
Type 'NSDirectoryEnumerator' does not conform to protocol 'SequenceType'
My aim is to look at all the subfolders and files contained into the main folder and find all the files with a certain extension to then do something with them.
Use the nextObject() method of enumerator:
while let element = enumerator?.nextObject() as? String {
if element.hasSuffix("ext") { // checks the extension
}
}
Nowadays (early 2017) it's highly recommended to use the – more versatile – URL related API
let fileManager = FileManager.default
do {
let resourceKeys : [URLResourceKey] = [.creationDateKey, .isDirectoryKey]
let documentsURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let enumerator = FileManager.default.enumerator(at: documentsURL,
includingPropertiesForKeys: resourceKeys,
options: [.skipsHiddenFiles], errorHandler: { (url, error) -> Bool in
print("directoryEnumerator error at \(url): ", error)
return true
})!
for case let fileURL as URL in enumerator {
let resourceValues = try fileURL.resourceValues(forKeys: Set(resourceKeys))
print(fileURL.path, resourceValues.creationDate!, resourceValues.isDirectory!)
}
} catch {
print(error)
}
I couldn't get pNre's solution to work at all; the while loop just never received anything. However, I did come across this solution which works for me (in Xcode 6 beta 6, so perhaps things have changed since pNre posted the above answer?):
for url in enumerator!.allObjects {
print("\((url as! NSURL).path!)")
}
my two cents from previously anwers.. more swifty and with optionals:
let enumerator = FileManager.default.enumerator(atPath: folderPath)
while let element = enumerator?.nextObject() as? String {
print(element)
if let fType = enumerator?.fileAttributes?[FileAttributeKey.type] as? FileAttributeType{
switch fType{
case .typeRegular:
print("a file")
case .typeDirectory:
print("a dir")
}
}
}
returns all files in a directory + in subdirectories
import Foundation
let path = "<some path>"
let enumerator = FileManager.default.enumerator(atPath: path)
while let filename = enumerator?.nextObject() as? String {
print(filename)
}
Swift3 + absolute urls
extension FileManager {
func listFiles(path: String) -> [URL] {
let baseurl: URL = URL(fileURLWithPath: path)
var urls = [URL]()
enumerator(atPath: path)?.forEach({ (e) in
guard let s = e as? String else { return }
let relativeURL = URL(fileURLWithPath: s, relativeTo: baseurl)
let url = relativeURL.absoluteURL
urls.append(url)
})
return urls
}
}
Based on code from #user3441734
Swift 3
let fd = FileManager.default
fd.enumerator(atPath: "/Library/FileSystems")?.forEach({ (e) in
if let e = e as? String, let url = URL(string: e) {
print(url.pathExtension)
}
})
In case that you are getting the
'NSDirectoryEnumerator?' does not have a member named 'nextObject' error
the while loop should be:
while let element = enumerator?.nextObject() as? String {
// do things with element
}
It has something to do with optional chaining
SWIFT 3.0
Returns all files with extension in the Directory passed & its subdirectories
func extractAllFile(atPath path: String, withExtension fileExtension:String) -> [String] {
let pathURL = NSURL(fileURLWithPath: path, isDirectory: true)
var allFiles: [String] = []
let fileManager = FileManager.default
let pathString = path.replacingOccurrences(of: "file:", with: "")
if let enumerator = fileManager.enumerator(atPath: pathString) {
for file in enumerator {
if #available(iOS 9.0, *) {
if let path = NSURL(fileURLWithPath: file as! String, relativeTo: pathURL as URL).path, path.hasSuffix(".\(fileExtension)"){
let fileNameArray = (path as NSString).lastPathComponent.components(separatedBy: ".")
allFiles.append(fileNameArray.first!)
}
} else {
// Fallback on earlier versions
print("Not available, #available iOS 9.0 & above")
}
}
}
return allFiles
}
Updating for Swift 3:
let fileManager = FileManager() // let fileManager = NSFileManager.defaultManager()
let en=fileManager.enumerator(atPath: the_path) // let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(folderPath)
while let element = en?.nextObject() as? String {
if element.hasSuffix("ext") {
// do something with the_path/*.ext ....
}
}
Adding to vadian's response -- the Apple docs mention that Path-based URLs are simpler in some ways, however file reference URLs have the advantage that the reference remains valid if the file is moved or renamed while your app is running.
From the documentation for "Accessing Files and Directories":
"Path-based URLs are easier to manipulate, easier to debug, and are generally preferred by classes such as NSFileManager. An advantage of file reference URLs is that they are less fragile than path-based URLs while your app is running. If the user moves a file in the Finder, any path-based URLs that refer to the file immediately become invalid and must be updated to the new path. However, as long as the file moved to another location on the same disk, its unique ID does not change and any file reference URLs remain valid."
https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html
If you want to categorically check whether an element is a file or a subdirectory:
let enumerator = FileManager.default.enumerator(atPath: contentsPath);
while let element = enumerator?.nextObject() as? String {
if(enumerator?.fileAttributes?[FileAttributeKey.type] as! FileAttributeType == FileAttributeType.typeRegular){
//this is a file
}
else if(enumerator?.fileAttributes?[FileAttributeKey.type] as! FileAttributeType == FileAttributeType.typeDirectory){
//this is a sub-directory
}
}
Recently struggled with this when handling an array of urls, whether they be a directory or not (eg. drag and drop). Ended up with this extension in swift 4, may be of use
extension Sequence where Iterator.Element == URL {
var handleDir: [URL] {
var files: [URL] = []
self.forEach { u in
guard u.hasDirectoryPath else { return files.append(u.resolvingSymlinksInPath()) }
guard let dir = FileManager.default.enumerator(at: u.resolvingSymlinksInPath(), includingPropertiesForKeys: nil) else { return }
for case let url as URL in dir {
files.append(url.resolvingSymlinksInPath())
}
}
return files
}
}
Avoid reference URLs, while they do have some advantages as stated above, they eat system resources and if you’re enumerating a large filesystem (not that large actually) your app will hit a system wall quickly and get shutdown by macOS.