Removing an item from an array of image paths - swift

I have a function that gathers all of the .jpg files in the Documents directory and all folders within it. The function then puts the file paths of the .jpgs into an array. What I need to do is filter a file defaultImage.jpg from the array. The problem I'm having is that the items in the array are paths to the jpg images so they are not strings. How can I filter either "theArray" or "files" or "theImagePaths" variables to remove the defaultImage.jpg? I've tried getting the index of defaultImage.jpg but again because the variables contain paths to the image files that doesn't seem to work.
I've tried - theArray.removeAll(where: {$0 == "defaultImage.jpg"}) but I couldn't get it to remove the image file.
static func buildPresentationArray() -> [String]
{
let theDirectoryPath = ModelData.getDocumentsDirectory()
let fm = FileManager.default
var theArray = [String]()
theArray.removeAll()
let files = fm.enumerator(at: theDirectoryPath, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])
let theImagePaths = files!.filter{ ($0 as AnyObject).pathExtension == "jpg"}
for theImagePath in theImagePaths
{
theArray.append((theImagePath as AnyObject).path)
}
return theArray
}

You can use a combination of filter(_:) and contains(_:) to get that working, i.e.
let filteredArray = theArray.filter({ !$0.contains("defaultImage.jpg") })

Try this :
theArray.removeAll { (image: String) -> Bool in
return image.contains("defaultImage.jpg")
}

From the URL get the last component and compare it with defaultImage.jpg
theArray = [URL]()
theArray.removeAll(where: {$0.lastPathComponent == "defaultImage.jpg"})

Related

Using Swift .split to format an array

I am reading in a text file of translation pairs of this format:
boy:garçon
garçon:boy
Into an array using the following code:
var vocab:[String:String] = [:]
let path = Bundle.main.path(forResource: "words_alpha", ofType: "txt")!
let text = try! String(contentsOfFile: path, encoding: String.Encoding.utf8)
let vocab = text.components(separatedBy: CharacterSet.newlines)
The imported array looks like this:
["boy:garçon", "garçon:boy"]
Whereas I would like the array to be formatted like this:
["boy":"garçon", "garçon":"boy"]
What is the best way to achieve the desired array format shown above using a Swift string transformation?
Have been trying to use .split, but with not much success.
Let's be clear:
["boy":"garçon", "garçon":"boy"]
That's a Dictionary, not an Array.
There a multiples ways to do that, here's two possible codes:
var manual: [String: String] = [:]
array.forEach { aString in
let components = aString.components(separatedBy: ":")
guard components.count == 2 else { return }
manual[components[0]] = components[1]
}
print(manual)
or
let reduced = array.reduce(into: [String: String]()) { result, current in
let components = current.components(separatedBy: ":")
guard components.count == 2 else { return }
result[components[0]] = components[1]
}
print(reduced)
Output (for both):
$> ["garçon": "boy", "boy": "garçon"]
As said, it's a Dictionary, so there is no guarantee that the print be:
["garçon": "boy", "boy": "garçon"] or ["boy":"garçon", "garçon":"boy"], it's key-value access, not index-value access.

How to speed up getting file information of 10.000s of files

I need to collect a lot of data about picture and movie files in a directory (or a directory tree). This includes file name, date of creation and modification, filesize (and maybe later on additional data like size of picture - if available - which is not covered here). There are 10.000s of them. Wenn requesting 17.000 files the programm needs 10 min for doing this task.
I'm doing it with the following code, but is there a way to speed it up significantly?
This is the code I use:
// get files matching extensions
func getPicFilenames(_ exts: [String]? = nil, includeFolders: Bool = false, skipHiddenFiles: Bool = true)
-> Array<URL>
{
// all to upper once (and drop optional dot)
let extUpper = exts == nil ?
[String]() :
exts!.map { ($0.hasPrefix(".") ? String($0.dropFirst(1)) : $0).uppercased() }
// let resourceKeys = [URLResourceKey.nameKey, URLResourceKey.isDirectoryKey]
// we also need size of files
let resourceKeys = [ URLResourceKey.nameKey, URLResourceKey.isDirectoryKey,
URLResourceKey.creationDateKey, URLResourceKey.contentModificationDateKey,
URLResourceKey.attributeModificationDateKey, URLResourceKey.fileSizeKey]
var fileURLs: [URL] = []
let directoryEnumerator = FileManager().enumerator(at: self, includingPropertiesForKeys: resourceKeys,
options: skipHiddenFiles ? [.skipsHiddenFiles] : [], errorHandler: nil)!
for case let fileURL as NSURL in directoryEnumerator {
guard let resourceValues = try? fileURL.resourceValues(forKeys: resourceKeys),
let isDirectory = resourceValues[URLResourceKey.isDirectoryKey] as? Bool
else {
continue
}
// we can skip complete descendants! >> directoryEnumerator.skipDescendants()
if !isDirectory || (isDirectory && includeFolders) {
if let ext = fileURL.pathExtension {
// matching extension? (ignore case!)
if extUpper.count == 0 || extUpper.contains((ext.uppercased())) {
fileURLs.append( fileURL.absoluteURL! )
}
}
}
}
return fileURLs
}
This needs 1 min per 1.700 files. Wenn analyzing 100.000s files it should be much faster (if possible).
I'm using Swift 4.2 on XCode 10.1 with OSX 10.11.6.

How to fetch data from 2 csv files in swift

My app currently gets data (points on map) from the .csv file San Francisco.
What should I change in my code to get data from San Francisco as well as Oakland, another .csv file that I have added?
func setupData() {
pointsDataSource = PointsDataSource(with: "San Francisco")
//if let pointsDataSource = pointsDataSource {
//map.addAnnotations(pointsDataSource.annotations)
//}
}
There are a few different methods for scanning a CSV file into your app using Swift. I found that I was able to create my own class method that I thought was the most useful.
I have to apologise because I haven't referenced the internet posts by other developers that helped me out - If I come accross them again then I'll definitely include them!
Here is the class:
import Foundation
class CSVScanner {
class func debug(string:String){
println("CSVScanner: \(string)")
}
class func runFunctionOnRowsFromFile(theColumnNames:Array<String>, withFileName theFileName:String, withFunction theFunction:(Dictionary<String, String>)->()) {
if let strBundle = NSBundle.mainBundle().pathForResource(theFileName, ofType: "csv") {
var encodingError:NSError? = nil
if let fileObject = NSString(contentsOfFile: strBundle, encoding: NSUTF8StringEncoding, error: &encodingError){
var fileObjectCleaned = fileObject.stringByReplacingOccurrencesOfString("\r", withString: "\n")
fileObjectCleaned = fileObjectCleaned.stringByReplacingOccurrencesOfString("\n\n", withString: "\n")
let objectArray = fileObjectCleaned.componentsSeparatedByString("\n")
for anObjectRow in objectArray {
let objectColumns = anObjectRow.componentsSeparatedByString(",")
var aDictionaryEntry = Dictionary<String, String>()
var columnIndex = 0
for anObjectColumn in objectColumns {
aDictionaryEntry[theColumnNames[columnIndex]] = anObjectColumn.stringByReplacingOccurrencesOfString("\"", withString: "", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
columnIndex++
}
if aDictionaryEntry.count>1{
theFunction(aDictionaryEntry)
}else{
CSVScanner.debug("No data extracted from row: \(anObjectRow) -> \(objectColumns)")
}
}
}else{
CSVScanner.debug("Unable to load csv file from path: \(strBundle)")
if let errorString = encodingError?.description {
CSVScanner.debug("Received encoding error: \(errorString)")
}
}
}else{
CSVScanner.debug("Unable to get path to csv file: \(theFileName).csv")
}
}
}
You can implement it in your code like this:
var myCSVContents = Array<Dictionary<String, String>>()
CSVScanner.runFunctionOnRowsFromFile(["title", "body", "category"], withFileName: "fileName.csv", withFunction: {
(aRow:Dictionary<String, String>) in
myCSVContents.append(aRow)
})
This will build an array of Dictionary objects, each representing a row from the CSV. You need to supply an array as the first parameter which contains the header labels of your csv document - make sure you include a label for every column!
However, feel free to skip adding the rows to an Array - you can run any function you like on each row. For instance, you may want to add these directly into a CoreData object.

read and write CSV files in Swift 3

I've seen different libraries like SwiftCSV, CSwiftV. AFAIK, they made for previous versions of swift. I need a very simple realization for swift 3 : open file, read line, put into array; or open, write array to csv file, and that's it. Any help?
I have:
struct Data {
var DataTime: Date = Date()
var Price: Double = 0.0
}
func DatafromCSV(_ CSV: String, _ separator: String) -> [Data] {
var x = [Data]()
//open file, read line, put into array, close file
return x
}
func DatatoCSV(_ CSV: String, _ separator: String) -> [Data] {
var x = [Data]()
//create file (erase data if exists, write data from array, close file
return x
}
Try to use CSVImporter Framework, is very simple. You can add it to your projects following this link: https://github.com/Flinesoft/CSVImporter
static func extractFromCSV(){
//the Path of the csv
let documentsUrl:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL!
let destinationFileUrl = documentsUrl.appendingPathComponent("name.csv")
let path = destinationFileUrl.path
let importer = CSVImporter<YourClassName?>(path: path,delimiter: ";")
importer.importRecords{ recordValues -> Sede_Azienda? in
//this piece of code is called for each row. You can access to each field with recordValues[x] and manage the data.
return nil //or the object if you need it
}
}
This is asynchronous but this framework has also some sync methods. Look the documentation.
NB: YourClassName is the type of class of the returned object if you need it.

Order a NSURL array

I'm loading a lot of image paths into a NSURL.
The images are in a folder ordered from 1.PNG, 2.PNG, 3.PNG to 1500.PNG. When I trie to load them:
let imagePath = path + "/images"
let url = NSURL(fileURLWithPath: imagePath)
print(url)
let fileManager = NSFileManager.defaultManager()
let properties = [NSURLLocalizedLabelKey,
NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey]
do {
imageURLs = try fileManager.contentsOfDirectoryAtURL(url, includingPropertiesForKeys: properties, options:NSDirectoryEnumerationOptions.SkipsHiddenFiles)
} catch let error1 as NSError {
print(error1.description)
}
The imageURLs array gets filled with:
imageURLs[0] = ...\0.PNG
imageURLs[1] = ...\1.PNG
imageURLs[2] = ...\100.PNG
imageURLs[3] = ...\1000.PNG
and not in the numeric order!
Can someone help to sort the imageURLs or while i load the image paths on it or after they are loaded?
As you want to sort the files by the number you have to parse first the path to achieve it, so let's suppose we have the following array of NSURL objects:
var urls = [NSURL(string: "file:///path/to/user/folder/2.PNG")!, NSURL(string: "file:///path/to/user/folder/100.PNG")!, NSURL(string: "file:///path/to/user/folder/101.PNG")!, NSURL(string: "file:///path/to/user/folder/1.PNG")! ]
We can use the pathComponents property to extract an array with all the components in the path for a NSURL (e.g ["/", "path", "to", "user", "folder", "2.PNG"]).
If we see we can order the files by the last element in the array that is the filename removing the extension and the dot("."), in this case the number. Let's see how to do it in the following code:
urls.sortInPlace {
// number of elements in each array
let c1 = $0.pathComponents!.count - 1
let c2 = $1.pathComponents!.count - 1
// the filename of each file
var v1 = $0.pathComponents![c1].componentsSeparatedByString(".")
var v2 = $1.pathComponents![c2].componentsSeparatedByString(".")
return Int(v1[0]) < Int(v2[0])
}
In the above code we use the function sortInPlace to avoid create another array with the elements sorted, but can you use sort instead if you want. The another important point in the code is the line return Int(v1[0]) < Int(v2[0]), in this line we have to convert the number in the string to a real number, because if we compare the two strings "2" and "100" the second one is less than greater than because the string are compared lexicographically.
So the the array urls should be like the following one:
[file:///path/to/user/folder/1.PNG, file:///path/to/user/folder/2.PNG, file:///path/to/user/folder/100.PNG, file:///path/to/user/folder/101.PNG]
EDIT:
The two functions pathComponents and componentsSeparatedByString increase the space complexity of the sortInPlace algorithm, if you can asure that the path for the files always will be the same except it's filename that should be a number you can use instead this code:
urls.sortInPlace { $0.absoluteString.compare(
$1.absoluteString, options: .NumericSearch) == .OrderedAscending
}
I hope this help you.
Its a bit late, anyway this worked for me in Swift 5
imageURLs.sort {
($0.pathComponents.last?.components(separatedBy: ".").first)! < ($1.pathComponents.last?.components(separatedBy: ".").first)!
}
This takes path components from url(separated by /), then take the last part and take components of it ( separated by . ). Then take first part and compare with the other urls similarly.
Complexity: O(n log n), where n is the length of the collection[ as per Apple doc]