Anthony
Anthony

Reputation: 143

Swift - Populate uitableview with dictionary of [String: [String: String]]

I'm new to Swift, and I am currently creating a diary app that asks the user questions. I'm storing the user's input like this:

dict = ["date": ["question1": "answer", "question2": "answer"]]

Now I need to display this data back to the user in a tableview, where "date" is a title and "question1" is the description.

I've looked online, but answers seem to reference "indexPath.row" for inputting information into a cell, but since this is a dictionary of strings, I can't do that.

Thank you for your help!

Upvotes: 3

Views: 1974

Answers (4)

Malith Kuruwita
Malith Kuruwita

Reputation: 642

You can use the dictionary as it is without changing

You should sort before use, remember

let dict = ["date": ["question1": "answer", "question2": "answer"]]

Number of sections

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

Title of the header

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return Array(dict)[section].key
}

Number of rows in section

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let key = Array(dict)[section].key
    return dict[key]?.count ?? 0
}

Cell for row at

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
   let key = Array(dict)[section].key
   if let questionsDict = dict[key] {
     let keyValue = Array(questionsDict)[indexPath.row]
     print("Question: \(keyValue.key), Answer: \(keyValue.value)")
   }
   return cell
}

Upvotes: 1

Ashley Mills
Ashley Mills

Reputation: 53121

Rather than using an array of dictionaries, you should consider using objects that better represent your data.

struct Question: {
    let question: String
    let answer: String
}

struct DiaryDay {
    let date: Date // Note this is a Date object, not a String
    let questions: [Question]
}

then you have

let diaryDays = DiaryDay(date: <date>, questions: 
    [Question(question: "question1": answer: "answer"),
     Question(question: "question2": answer: "answer")])

while there's a bit more code, going forward you'll find it easier to see what's happening.

It looks like you should have a section per diary day…

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

and then one row per question…

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let diaryDay = diaryDays[section]
    return diaryDay.questions.count
}

and then configure your cell…

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // dequeue cell
    let diaryDay = diaryDays[indexPath.section]
    let question = diaryDay.questions[indexPath.row]    
    cell.question = question
    return cell
}

and show the date in the section header…

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    let diaryDay = diaryDays[section]

    return // formatted diaryDay.date
}

Upvotes: 5

kathayatnk
kathayatnk

Reputation: 1015

you will have to do a little preparation before you can display data from the dictionary type you are using. Also remember the dictionary is not order list so which order the data will be printed solely depends on system. One approach would be the following

var data = ["date1":["q1":"A1","q2":"A2","q3":"A3"],"date2":["q1":"A1","q2":"A2","q3":"A3"]] . //This is data from your example
var displayableData = [(title: String, qAndA: [(question: String, answer: String)])]() //this is what we will be needing


override func viewDidLoad() {
    super.viewDidLoad()

    //convert the whole dictionary to tuple
    displayableData = data.map { ($0.key, $0.value.map{ ($0.key, $0.value)})}

    //here we have converted the dictionary to what we need
}


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

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

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 55.0
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    let currentQA = displayableData[indexPath.section].qAndA[indexPath.row]
    cell.textLabel?.text = "\(currentQA.question) -> \(currentQA.answer)"
    return cell
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 30.0
}

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 30.0))
    view.backgroundColor = UIColor.lightGray
    let label = UILabel(frame: CGRect(x: 10, y: 0, width: view.bounds.width - 20, height: 30.0))
    label.text = displayableData[section].title
    view.addSubview(label)
    return view
}

This is the output

Upvotes: 2

AshvinGudaliya
AshvinGudaliya

Reputation: 3314

You can try out using map. here Dictionary converts into Array of Dictionary.

let dict = ["date": ["question1": "answer", "question2": "answer"]]

if let value = dict["date"] {
    let v = value.map {
        ["question": $0.key, "answer": $0.value]
    }

    debugPrint(v)
}

Upvotes: 0

Related Questions