Reputation: 133
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
}
}
Upvotes: 0
Views: 87
Reputation: 63271
I suggest against CrimeNameSection
. Just use a dictionary from Character
to [CrimeInfo]
. It'll be much easier to work with.
extension Array {
func groupBy<Key>(_ keyGenerator: (Element) -> Key)
-> [Key: [Element]] {
var result = [Key: [Element]]()
for element in self {
let key = keyGenerator(element)
var array = result[key] ?? []
array.append(element)
result[key] = array
}
return result
}
}
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: ""),
]
let crimesByInitial = crimes.groupBy{ $0.name.characters.first! }
result:
[
"b": [
CrimesInfo(name: "ba", detail: "", time: ""),
CrimesInfo(name: "bb", detail: "", time: "")
],
"a": [
CrimesInfo(name: "aa", detail: "", time: ""),
CrimesInfo(name: "ab", detail: "", time: ""),
CrimesInfo(name: "ac", detail: "", time: "")
],
"c": [
CrimesInfo(name: "ca", detail: "", time: "")
]
]
Upvotes: 0
Reputation: 59506
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: "")])
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)
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
}
}
Upvotes: 2