How to fetch data from 2 csv files in swift - 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.

Related

How do I apply my custom attribute in an AttributedString in SwiftUI

I have my custom attribute here, just for testing, the goal is to swap letters just to see how I can affect the string
enum SwapAttribute : AttributedStringKey {
typealias Value = String
static let name = "swap"
}
extension AttributeScopes {
struct MyTextStyleAttributes: AttributeScope {
let swap: SwapAttribute
}
}
extension AttributeDynamicLookup {
subscript<T: AttributedStringKey>(dynamicMember keyPath: KeyPath<AttributeScopes.MyTextStyleAttributes, T>) -> T {
return self[T.self]
}
}
func attributesTest() {
var str = AttributedString("It's a secret")
let secret = str.range(of: "secret")!
str[secret].swap = "*"
// expect 'It's a ******'
print(str)
}
I can add my attribute, and I can see it in the .runs variable, but all of the documentation and tutorials gloss over this. How do I execute code or apply changes to the string.
(or perhaps this is intended for other things)
Custom Implementations
Custom attributes only serve as data storage, they are not displayed or processed by SwiftUI or UIKit. Additional custom behavior would have to be implemented manually.
Workaround
For behavior as described in the question, stick to extensions.
var str = AttributedString("It's a ******")
let secret = str.range(of: "******")!
str[secret].swap = "secret"
That would display "It's a ******", while still holding your "secret" data in the attributes.
To improve on that, you could create an extension:
extension AttributedString {
func swapping(_ value: String, with new: Character) -> AttributedString {
let mutableAttributedString = NSMutableAttributedString(self)
let range = mutableAttributedString.mutableString.range(of: value)
let newString = String(repeating: new, count: range.length)
// Use replaceCharacters(in:with:) to keep all original attributes
mutableAttributedString.replaceCharacters(in: range, with: newString)
// Set the "secret" data
var str = AttributedString(mutableAttributedString)
str[str.range(of: newString)!].swap = value
return str
}
}
var str = AttributedString("It's a secret")
.swapping("secret", with: "*")
print(str)
The print output:
It's a {
}
****** {
swap = secret
}

ITunes Library Framework in Swift 5.2

I have searched everywhere and I am unable to find a swifty example of how to use the Apple ITunesLibraryFramework. I have been trying to figure out how to use this Framework in Swift 5.2.
I want to get information directly from the Music library rather than having to rely on a XML library export file.
I have the below code in my playground and it prints nothing legible. How would I access the fields for playlists and mediaItems and be able to read them in human readable form?
I have installed the framework in the project. This is my project playground code:
import Foundation
import iTunesLibrary
var library:ITLibrary
do {
let library = try ITLibrary(apiVersion: "1.1")
let mediaItems = library.allMediaItems
let playlists = library.allPlaylists
print("Media Folder Location - \(String(describing: library.mediaFolderLocation))")
print("\nPlaylists - \(playlists)")
print("\nTracks - \(mediaItems)")
} catch let error as NSError {
print(error)
}
This is the ITLibrary.m file that I imported the header:
#import <Foundation/Foundation.h>
#import <iTunesLibrary/iTunesLibrary.h>
When I run the above code in the project playground all I get is a bunch of binary data for both playlists and mediaItems. All I want to do is iterate over the data and collect information from the library for use in my program. It's probably something easy, but I haven't found it yet.
EDIT: - After using #Vincent's answer I ran into another problem with the following code:
import Foundation
import iTunesLibrary
let library = try ITLibrary(apiVersion: "1.1")
typealias tracks = [NSNumber:TrackInfo]
var trackInfo = TrackInfo()
struct TrackInfo {
var title = ""
var artist = ""
var album = ""
var totalTime = 0
var year = 0
var persistentID = ""
var location:URL!
}
let songs = library.allMediaItems
let playlists = library.allPlaylists
for playlist in playlists {
if playlist.name.lowercased().contains("test-") {
print("New Playlist---------------\nName: \(playlist.name)")
for song in playlist.items {
trackInfo.title = song.title
print("\n\nNew Track-----------------\nTitle: \(trackInfo.title)")
if song.artist?.name != nil {
trackInfo.artist = song.artist?.name as! String
}
print("Artist: \(trackInfo.artist)")
trackInfo.album = song.album.title!
print("Albumn Name: \(trackInfo.album)")
trackInfo.totalTime = song.totalTime
print("Total Time: \(trackInfo.totalTime)")
trackInfo.year = song.year
print("Year: \(trackInfo.year)")
trackInfo.location = song.location!
print("Location: \(trackInfo.location!)")
var persistentID = song.persistentID
tracks.updateValue(song.persistentID, trackInfo)
}
}
}
The issue I'm having is getting the tracks info into the trackInfo dictionary. I'm trying to use the track persistentID (NSNumber) as the key for the dictionary, which I have declared. For some reason it isn't allowing me to use it.
Here's how you can have it print each playlist and track:
Each ITLibPlaylist or ITLibMediaItem object contains many information about each playlist/media item. To get only the name/title of each, you will have to iterate through the results to retrieve them.
For this example below, the name of each playlist's name is printed.
print("\nPlaylists -")
for playlist in playlists {
print(playlist.name)
}
Which will print (for example):
Playlists -
Library
Music
For this example below, the name of each track's name is printed.
print("\nTracks -")
for mediaItem in mediaItems {
print(mediaItem.title)
}
Which will print (for example):
Tracks -
Ev'ry Time We Say Goodbye
My Favorite Things
But Not for Me
Summertime
Edit: Here's the secondary solution to the secondary problem:
First things first, a dictionary should be initialised, instead of using typealias.
typealias only makes an alias for a pre existing type, like
typealias NumberWithAlotOfDecimals = Double
let a: NumberWithAlotOfDecimals = 10.1
let b: Double = 10.1
both will a and b are Double, as NumberWithAlotOfDecimals is just an alias for Double.
Here's how to initialise:
//typealias tracks = [NSNumber:TrackInfo] // not this
var tracks = [NSNumber:TrackInfo]() // but this
Secondly, nullable objects should be properly handled
if let artistName = song.artist?.name {
trackInfo.artist = artistName
}
and
if let title = song.album.title {
trackInfo.album = title
}
if let location = song.location {
trackInfo.location = location
print("Location: \(location)")
}
instead of
if song.artist?.name != nil {
trackInfo.artist = song.artist?.name as! String
}
Please do not use ! to force unwrap nullable objects as that will cause runtime crashes when the object is nil.
Lastly, this is the way to store key value into dictionary in Swift.
let persistentID = song.persistentID
//tracks.updateValue(song.persistentID, trackInfo) // not this
tracks[persistentID] = trackInfo // this

Swift Get Next Page from header of NSHTTPURLResponse

I am consuming an API that gives me the next page in the Header inside a field called Link. (For example Github does the same, so it isn't weird.Github Doc)
The service that I am consuming retrieve me the pagination data in the following way:
As we can see in the "Link" gives me the next page,
With $0.response?.allHeaderFields["Link"]: I get </api/games?page=1&size=20>; rel="next",</api/games?page=25&size=20>; rel="last",</api/games?page=0&size=20>; rel="first".
I have found the following code to read the page, but it is very dirty... And I would like if anyone has dealt with the same problem or if there is a standard way of face with it. (I have also searched if alamofire supports any kind of feature for this but I haven't found it)
// MARK: - Pagination
private func getNextPageFromHeaders(response: NSHTTPURLResponse?) -> String? {
if let linkHeader = response?.allHeaderFields["Link"] as? String {
/* looks like:
<https://api.github.com/user/20267/gists?page=2>; rel="next", <https://api.github.com/user/20267/gists?page=6>; rel="last"
*/
// so split on "," the on ";"
let components = linkHeader.characters.split {$0 == ","}.map { String($0) }
// now we have 2 lines like '<https://api.github.com/user/20267/gists?page=2>; rel="next"'
// So let's get the URL out of there:
for item in components {
// see if it's "next"
let rangeOfNext = item.rangeOfString("rel=\"next\"", options: [])
if rangeOfNext != nil {
let rangeOfPaddedURL = item.rangeOfString("<(.*)>;", options: .RegularExpressionSearch)
if let range = rangeOfPaddedURL {
let nextURL = item.substringWithRange(range)
// strip off the < and >;
let startIndex = nextURL.startIndex.advancedBy(1) //advance as much as you like
let endIndex = nextURL.endIndex.advancedBy(-2)
let urlRange = startIndex..<endIndex
return nextURL.substringWithRange(urlRange)
}
}
}
}
return nil
}
I think that the forEach() could have a better solution, but here is what I got:
let linkHeader = "</api/games?page=1&size=20>; rel=\"next\",</api/games?page=25&size=20>; rel=\"last\",</api/games?page=0&size=20>; rel=\"first\""
let links = linkHeader.components(separatedBy: ",")
var dictionary: [String: String] = [:]
links.forEach({
let components = $0.components(separatedBy:"; ")
let cleanPath = components[0].trimmingCharacters(in: CharacterSet(charactersIn: "<>"))
dictionary[components[1]] = cleanPath
})
if let nextPagePath = dictionary["rel=\"next\""] {
print("nextPagePath: \(nextPagePath)")
}
//Bonus
if let lastPagePath = dictionary["rel=\"last\""] {
print("lastPagePath: \(lastPagePath)")
}
if let firstPagePath = dictionary["rel=\"first\""] {
print("firstPagePath: \(firstPagePath)")
}
Console output:
$> nextPagePath: /api/games?page=1&size=20
$> lastPagePath: /api/games?page=25&size=20
$> firstPagePath: /api/games?page=0&size=20
I used components(separatedBy:) instead of split() to avoid the String() conversion at the end.
I created a Dictionary for the values to hold and removed the < and > with a trim.

How can I bring in data from a text file on disk without adding it to the project?

I'm trying to read in a file from disk and parse its data into a nice format. However, the function is not returning anything. It returns an empty array. Why is this?
Note: I've been tinkering around with this and I've managed to simplify the previous 2 functions to just this one.
The function:
func openFile(_ fileName:String, _ fileType:String) -> [(Double, Double)] {
let file = fileName + fileType //this is the file. we will write to and read from it
var text2: String = ""
if let dir = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first {
let path = dir.appendingPathComponent(file)
//reading
do {
text2 = try String(contentsOf: path, encoding: String.Encoding.utf8)
}
catch {/* error handling here */}
}
var pairs = [(Double,Double)]()
var words: [String] = []
for line in (text2.components(separatedBy: "\n").dropFirst()){
if line != "" {
words = line.components(separatedBy: "\t")
pairs.append((Double(words[0])!,Double(words[1])!))
}
}
return pairs
}

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.