0

I have a Dictionary data structure like below and I am trying to group them in my TableViewController such that Group A displays MyData that starts with title = A and at the same time display sectionIndexTitlesForTableView with available letters gotten from Title.

[This is my what I want to achieve]

Prototype

I have tried to scrap off all the first letters from the title Element in my Dictionary and save them in a set using the code below but when I run my app, I get results duplicated in my table.

I am quite new to swift and would be glad to be guided on how to achieve this.

Here's my Dictionary Data:

var data: [[String:AnyObject]] =

[
  [
    "id": "1",
    "title": "A Title",
    "alphabet": "A",
    "Detail": "This is a String"
  ],      
  [
    "id": "2",
    "title": "A Title Again",
    "alphabet": "A",
    "Detail": "This is a String"
  ],
  [
    "id": "3",
    "title": "B Title",
    "alphabet": "B",
    "Detail": "This is a String"
  ],
  [
    "id": "4",
    "title": "B Title Again",
    "alphabet": "B",
    "Detail": "This is a String"
  ]
]

And Here's my attempt:

class Index: UITableViewController {

var MyData = data

var letters = Set<String>()


override func viewDidLoad() {
    super.viewDidLoad()
    for element in MyData {
        var title = element["title"] as? String
        let letter = title?.substringToIndex(advance(title!.startIndex, 1))         
        letters.insert(letter!)
    }


  MyData =   MyData.sort { element1, element2 in
        let title1 = element1["title"] as? String
        let title2 = element2["title"] as? String

        return title1 < title2
    }

}

// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return letters.count
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return self.MyData.count
}


override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell?

        cell!.textLabel?.text = (MyData[indexPath.row]["title"] as! String)

    return cell!

}
0x0
  • 363
  • 1
  • 4
  • 19

1 Answers1

0

The problem is numberOfRowsInSection has to return the number of rows per section, in your example 2 for section 0 and 2 for section 1

You can collect your letter set with the key value coding method valueForKey which is often mistaken for objectForKey.
Unlike objectForKey which returns one value for the given key valueForKey returns the value of the key alphabetof all members in the array.

This code creates a Set of the letters to purge the duplicates, turns it back to an Array and sorts it.

let letters = (data as NSArray).valueForKey("alphabet") as! [String]
let filteredLetters = Set<String>(letters)
let sortedLetters = Array(filteredLetters).sorted {$0 < $1}

If all values for alphabet – as well as the other keys - are guaranteed to be String there is no need to cast them to optionals.

Then in numberOfRowsInSection you have to filter the number of items of each section

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   return data.filter { ($0["alphabet"] as! String) == sortedLetters[section] }.count
}

Notice that there is no casting needed for the expression sortedLetters[section] because the compiler knows that's an array of String.

Of course you have also to retrieve the appropriate items for the sections in cellForRowAtIndexPath which is quite expensive because the main array is going to be filtered multiple times.
I'd recommend to transform data in viewDidLoad() into a new dictionary with the letters as keys and an array containing the items starting with this particular letter as values. This is the best solution regarding speed and performance.

Here a complete solution (without displaying the letters for quick search)

class TableViewController: UITableViewController {

  let data: [[String:String]] =

  [
    [
      "id": "1",
      "title": "A Title",
      "alphabet": "A",
      "Detail": "This is a String"
    ],
    [
      "id": "2",
      "title": "A Title Again",
      "alphabet": "A",
      "Detail": "This is a String"
    ],
    [
      "id": "3",
      "title": "B Title",
      "alphabet": "B",
      "Detail": "This is a String"
    ],
    [
      "id": "4",
      "title": "B Title Again",
      "alphabet": "B",
      "Detail": "This is a String"
    ]
  ]


  var letters = [String]()
  var dataSource = [String:AnyObject]()

  override func viewDidLoad() {
    super.viewDidLoad()

    for value in data  {
      let letter = value["alphabet"]!
      if dataSource[letter] == nil {
        letters.append(letter)
        dataSource[letter] = [[String:AnyObject]]()
      }
      var array = dataSource[letter] as! [[String:AnyObject]]
      array.append(value)
      dataSource.updateValue(array, forKey: letter)
    }
    letters.sorted {$0 < $1}
    tableView.reloadData()

  }

  // MARK: - Table view data source

  override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return letters.count
  }

  override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let letter = letters[section]
    return dataSource[letter]!.count
  }


  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell

    let letter = letters[indexPath.section]
    let letterArray = dataSource[letter]! as! [[String:AnyObject]]
    let item = letterArray [indexPath.row]
    if let title = item["title"] as? String {
      cell.textLabel?.text = title
    }
    return cell
  }

  override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return letters[section]
  }
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Many thanks for the response. I wish you would guide in relation to my code so as to make me understand better. I am just a complete Beginner at Swift. – 0x0 Aug 23 '15 at 13:12
  • 1
    Your code cannot work this way and it's too complicated to provide a solution based on your code. I'll post a complete solution – vadian Aug 23 '15 at 13:38