Kevin
Kevin

Reputation: 244

How to store a Swift array of custom objects in a text file?

I have a 2D generic array, with about 50 of these things:

[string, string, string, string, double, double]

The values are all in the same order, and I have a custom class:

class tramStop {
    var tramDirection: String?
    var stopCode: String?
    var nameEnglish: String?
    var nameChinese: String?
    var latitude: Double?
    var longitude: Double?

    init(direction: String, code: String, nameEn: String, nameCn: String, lat: Double, lon: Double) {
        tramDirection = direction
        stopCode = code
        nameEnglish = nameEn
        nameChinese = nameCn
        latitude = lat
        longitude = lon
    }
}

I would like to store the array as an array of custom objects in a file. I can't seem to figure out how to do this without creating something that appends an empty array every time. I feel like there's an easy (and proper) way to do this, but I can't figure it out.

Eventually what I would like to do, is take the device location, and find the nearest latitude and longitude object, and retrieve strings from it.

Upvotes: 0

Views: 1508

Answers (2)

Leo Dabus
Leo Dabus

Reputation: 236360

First of all it is Swift convention to name your classes and structures starting with an uppercase letter. I recommend using a struct instead of a class. Start declaring all your properties constants and add a custom initializer that takes your array of Any type objects (IMO you should be using a dictionary as a source). Add a guard statement to make sure the array count is equal to 6 and properly initialize each of your struct properties. To allow persistence you can add a computed property to create a dictionary with its own info and another property to convert your json object to data:

struct TramStop {
    let tramDirection: String
    let stopCode: String
    let nameEnglish: String
    let nameChinese: String
    let latitude: Double
    let longitude: Double

    init?(array: [Any]) {
        guard array.count == 6 else { return nil }

        tramDirection = array[0] as? String ?? ""
        stopCode      = array[1] as? String ?? ""
        nameEnglish   = array[2] as? String ?? ""
        nameChinese   = array[3] as? String ?? ""
        latitude      = array[4] as? Double ?? 0
        longitude     = array[5] as? Double ?? 0
    }

    init(_ dictionary: [String: Any]) {
        tramDirection = dictionary["tramDirection"] as? String ?? ""
        stopCode      = dictionary["stopCode"]      as? String ?? ""
        nameEnglish   = dictionary["nameEnglish"]   as? String ?? ""
        nameChinese   = dictionary["nameChinese"]   as? String ?? ""
        latitude      = dictionary["latitude"]      as? Double ?? 0
        longitude     = dictionary["longitude"]     as? Double ?? 0
    }

    var dictionary: [String: Any] {
        return [ "tramDirection" : tramDirection,
                 "stopCode"      : stopCode,
                 "nameEnglish"   : nameEnglish,
                 "nameChinese"   : nameChinese,
                 "latitude"      : latitude,
                 "longitude"     : longitude
        ]
    }

    var data: Data { return (try? JSONSerialization.data(withJSONObject: dictionary)) ?? Data() }
    var json: String { return String(data: data, encoding: .utf8) ?? String() }
}

And create a struct to hold your tramStops array and the proper initializers (array2D and jsonData):

struct Stops {

    let tramStops: [TramStop]

    init(_ array2D: [[Any]]) {
        tramStops = array2D.flatMap(TramStop.init)
    }

    init?(_ data: Data) {
        guard let array = (try? JSONSerialization.jsonObject(with: data)) as? [[String: Any]]
        else { return nil }
        tramStops = array.map(TramStop.init)
    }

    var array: [[String: Any]] {
        return tramStops.map{ $0.dictionary }
    }
    var data: Data {
        return (try? JSONSerialization.data(withJSONObject: array)) ?? Data()
    }
    var json: String {
        return String(data: data, encoding: .utf8) ?? String()
    }
}

Playground testing

let array2D = [["a1", "a2", "a3", "a4", 1.0, 2.0],
           ["b1", "b2", "b3", "b4", 3.0, 4.0]]

let stops = Stops(array2D)
print(stops.json)           // "[{"nameEnglish":"a3","latitude":1,"tramDirection":"a1","longitude":2,"stopCode":"a2","nameChinese":"a4"},{"nameEnglish":"b3","latitude":3,"tramDirection":"b1","longitude":4,"stopCode":"b2","nameChinese":"b4"}]"

let jsonURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("TramStop.json")

stops.data    // 209 bytes
for tramStop in stops.tramStops {
    print(tramStop.dictionary)
    print(tramStop.stopCode)
    print(tramStop.nameEnglish)
    print(tramStop.nameChinese)
    print(tramStop.latitude)
    print(tramStop.longitude)
}

do {
    try stops.data.write(to: jsonURL)
    print("json data saved")
} catch  {
    print(error)
}

Reading json data from fileURL:

do {
    let data = try Data(contentsOf: jsonURL)
    print(String(data: data, encoding: .utf8) ?? "")
    if let stops = Stops(data) {
        for tramStop in stops.tramStops {
            print(tramStop.tramDirection)
            print(tramStop.stopCode)
            print(tramStop.nameEnglish)
            print(tramStop.nameChinese)
            print(tramStop.latitude)
            print(tramStop.longitude)
        }
    }
} catch {
    print(error)
}

Upvotes: 1

Eggsalad
Eggsalad

Reputation: 383

You need to loop through the array, taking each set of 6 elements and initializing a tramStop to put in a new tramStop array. Or, better, initialize tramStops in the first place, without creating the interim array.

There are several ways to save to a file, depending on what you want to do with it. NSCoding might be applicable, and it's fairly easy to make your class conform to it. Check out this article.

EDIT: Here's how you can create the new tramStop array:

var newArray: [tramStop] = []

for i in stride(from: 0, to: oldArray.count, by: 6) {
    newArray.append(tramStop(
        direction: oldArray[i] as! String,
        code: oldArray[i + 1] as! String,
        nameEn: oldArray[i + 2] as! String,
        nameCn: oldArray[i + 3] as! String,
        lat: oldArray[i + 4] as! Double,
        lon: oldArray[i + 5] as! Double
    ))
}

But, as I said, I'd look into how to eliminate oldArray altogether.

Also, I didn't know you wanted a text file when I suggested NSCoding, which won't give you a text file.

Upvotes: 0

Related Questions