Tom G Tom G - 24 days ago 7
iOS Question

How to get sorted struct into another struct/dictionary using swift3?

I'm trying to create alphabetic sections in my TableView, so far I'v managed to get my database into struct and get the fist letter of every name from my database and sort it for the section header.
My problem is that I don't get the sorted variable into the another struct and show it in the table.

My structs:

struct CrimesInfo {

let name: String
let detail: String
let time: String


init(name:String, detail: String, time: String) {
self.name = name
self.detail = detail
self.time = time
}

init(fromResultSet: FMResultSet) {
self.init(
name: fromResultSet.string(forColumn: "Name"),
detail: fromResultSet.string(forColumn: "Detail"),
time: fromResultSet.string(forColumn: "Time")
)

}
}

struct CrimeNameSection {

var firstLetter: Character
var crimes: [CrimesInfo]

init(title: Character, objects: [CrimesInfo]) {
firstLetter = title
crimes = objects
}
}


My database store in struct 'CrimesInfo', after the sorting I want to insert it into the struct 'CrimeNameSection' (title: the first letter of 'name', objects: the rest of the data accordingly).

My code:

class SectionData {

var crimeInfo : [CrimesInfo] = []

func getCrimesData() {
crimeInfo = ModelManager.getInstance().getAllCrimeInfo() // get the database into the struct
}

func getSectionFromData() -> [CrimeNameSection] { // get the fisrt letter of 'name', sort it and get in the another struct
var crimeIndex = [Character: [CrimesInfo]]()
var CrimeSections = [CrimeNameSection]()
for crime in crimeInfo {
if let firstCharacter = crime.name.characters.first {
if crimeIndex[firstCharacter] == nil {
crimeIndex[firstCharacter] = [crime]
} else {
crimeIndex[firstCharacter]?.append(crime)
}
}
}

let sortedIndex = crimeIndex.sorted { $0.0 < $1.0 } // type: [(key: Character, value:[CrimesInfo])]

for key in sortedIndex { // get the sorted data into struct 'CrimeNameSection'
let sortedSections = CrimeNameSection(title: sortedIndex(key), objects: sortedIndex(value)) // error: 'Use of unresolved identifier 'value'
}

CrimeSections.append(sortedSections)

return CrimeSections
}
}

Answer

Given a list of CrimeInfo

let crimes = [
    CrimesInfo(name: "ba", detail: "", time: ""),
    CrimesInfo(name: "aa", detail: "", time: ""),
    CrimesInfo(name: "ab", detail: "", time: ""),
    CrimesInfo(name: "ca", detail: "", time: ""),
    CrimesInfo(name: "ac", detail: "", time: ""),
    CrimesInfo(name: "bb", detail: "", time: ""),
]

you can write

let sections: [CrimeNameSection] = crimes
    .sorted { $0.name < $1.name }
    .reduce([CrimeNameSection]()) { result, crime -> [CrimeNameSection] in
        let crimeFirstLetter = crime.name.characters.first ?? " "

        guard var index = result.index(where: { $0.firstLetter == crimeFirstLetter }) else {
            let newSection = CrimeNameSection(title: crimeFirstLetter, objects: [crime])
            return result + [newSection]
        }

        var result = result
        var section = result[index]
        section.crimes.append(crime)
        result[index] = section
        return result
    }

and you get this output

print(sections[0])
// CrimeNameSection(firstLetter: "a", crimes: [CrimesInfo(name: "aa", detail: "", time: ""), CrimesInfo(name: "ab", detail: "", time: ""), CrimesInfo(name: "ac", detail: "", time: "")])

print(sections[1])
// CrimeNameSection(firstLetter: "b", crimes: [CrimesInfo(name: "ba", detail: "", time: ""), CrimesInfo(name: "bb", detail: "", time: "")])

print(sections[2])
// CrimeNameSection(firstLetter: "c", crimes: [CrimesInfo(name: "ca", detail: "", time: "")])

Just a note

Let's look at your CrimeNameSection.

struct CrimeNameSection {

    var firstLetter: Character
    var crimes: [CrimesInfo]

    init(title: Character, objects: [CrimesInfo]) {
        firstLetter = title
        crimes = objects
    }
}

you named the second param of the initializer objects. This is semantically wrong since CrimeInfo is not an object, is a value.

Why don't you simply remove the initializer and the the struct to expose the memberwise initializer? Look

struct CrimeNameSection {
    var firstLetter: Character
    var crimes: [CrimesInfo]
}

now you can write

CrimeNameSection(firstLetter: "a", crimes: crimes)

Update

This is how you populate your table view

class Table: UITableViewController {

    var sections: [CrimeNameSection] = ...

    override func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sections[section].crimes.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let crime: CrimesInfo = sections[indexPath.section].crimes[indexPath.row]

        // TODO: use crime to populate your cell
    }

}